package com.jt.www.biz.order_pc.service.impl;


import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageInfo;
import com.jt.www.admin.market.service.CommissionService;
import com.jt.www.biz.order_pc.service.EndorseService;
import com.jt.www.common.service.FileService;
import com.jt.www.dao.mapper.endorse.EndorseMapper;
import com.jt.www.dao.mapper.endorse.SaveEndorseParamMapper;
import com.jt.www.domain.enums.common.parser.ReturnType;
import com.jt.www.domain.enums.common.parser.SaveType;
import com.jt.www.domain.enums.pc.SaveEndorseIsInsEnum;
import com.jt.www.domain.qo.EndorsQueryParam;
import com.jt.www.domain.qo.EndorsQueryResp;
import com.jt.www.domain.qo.EndorsementsEduQueryVO;
import com.jt.www.domain.vo.EduPolicyVO;
import com.jt.www.enums.endorse.*;
import com.jt.www.enums.issue.AppStatusEnum;
import com.jt.www.exception.BizException;
import com.jt.www.exception.ServiceException;
import com.jt.www.model.entity.endorse.EndorseEntity;
import com.jt.www.model.param.PageParam;
import com.jt.www.model.param.endorse.AppDetailProParam;
import com.jt.www.model.param.endorse.EndorseListParam;
import com.jt.www.model.param.endorse.EndorseParam;
import com.jt.www.model.param.endorse.SaveEndorseParam;
import com.jt.www.model.pay.BizResQo;
import com.jt.www.model.reps.GenericDataResponse;
import com.jt.www.model.reps.GenericListResponse;
import com.jt.www.model.vo.endorse.*;
import com.jt.www.model.vo.sdCheck.HolderForProVO;
import com.jt.www.model.woxuebao.toubao.ContextExtend;
import com.jt.www.remote.ParserClient;
import com.jt.www.remote.PolClient;
import com.jt.www.remote.ins.InsClient;
import com.jt.www.util.*;
import feign.Response;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.entity.ContentType;
import org.assertj.core.util.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import tk.mybatis.mapper.weekend.Weekend;
import tk.mybatis.mapper.weekend.WeekendCriteria;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.time.Instant;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.jt.www.domain.enums.common.parser.ReturnType.ALL;
import static com.jt.www.domain.enums.common.parser.ReturnType.DETAIL;
import static com.jt.www.enums.endorse.GradeEnum.getCodeByMsg;

@Service
@SuppressWarnings("unchecked")
public class EndorseServiceImpl implements EndorseService {

    public static final Logger log = LoggerFactory.getLogger(EndorseServiceImpl.class);
    private static final String ADDITIONAL_INSURED = "additionalinsured";
    private static final String INSURANCE_ID = "insuranceId";
    private static final String CREATE_TIME = "createTime";
    private static final String IS_DELETED = "isDeleted";
    private static final String CARD_NO = "cardno";
    private static final String STATUS = "status";
    private static final String GRADE = "grade";
    private static final String NO = "no";
    private static final String NAME = "name";
    private static final String SEX = "sex";
    private static final String STUDENTTEACHER = "studentteacher";
    private static final String CLZ = "clz";
    // 分页每页数量
    private static final Integer PS = 1000;
    // 返回错误信息的阈值
    private static final Integer MSG_NUM = 20;
    private static final String CHANNEL_CODE = "edu";
    //全国实责
    private static final String SZ_PRODUCT_CODE = "19PR789827";
    //全国校责
    private static final String XZ_PRODUCT_CODE = "19PR961660";
    //云南教职工
    private static final String YNJZG_PRODUCT_CODE = "19PR467834";
    //云南四款产品
    private static final List<String> YN_PRODUCT_CODES = Arrays.asList("19PR233356", "19PR452292", "19PR467834", "19PR534934");
    //全国实责校责产品
    private static final List<String> QG_PRODUCT_CODES = Arrays.asList("19PR789827", "19PR961660");
    /**
     * 出单缓存key
     */
    private static final String INSURANCE_ENDORSE_LOCK_KEY = "EDU_INSURANCE_ENDORSE";
    private static final String EDU_INSURANCE_LOCK_KEY = "EDU_INSURANCE";
    private static final String EDU_ENDORSE_LOCK_KEY = "EDU_ENDORSE";
    //云南教职工
    private static final String PAUSE = "pause";
    //优惠方式：5%
    private static final String FIVE_PERCENT = "FIVE_PERCENT";
    // 注入模板引擎
    @Autowired
    TemplateEngine templateEngine;
    @Value("${jtpf.benefit-rate}")
    private String benefitRate;
    @Autowired
    private PolClient polClient;
    @Autowired
    private EndorseMapper endorseMapper;
    @Autowired
    private SaveEndorseParamMapper endorseParamMapper;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private InsClient insClient;
    @Autowired
    private ParserClient parserClient;

    @Autowired
    private FileService fileService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private CommissionService commissionService;


    @Override
    public EndorseResponseVo uploadEndorse(EndorseParam param, MultipartFile excelFile) {
        EndorseResponseVo responseVo = null;
        try {
            // 险种类型：0-实责险（企业），1-实责险（学校），2-校责险（学校）... PolicyTypeEnum
            Integer policyType = param.getPolicyType();
            // 错误信息列表
            Set<String> errorMsgList = new HashSet<>();
            // 返回对象
            responseVo = new EndorseResponseVo();
            responseVo.setErrorMsgList(errorMsgList);
            // excel清单解析对象列表
            List<EndorseParseVo> parseVos;
            // 上传的文件id
            String fileId;
            // 上传人数
            Integer counts;
            // 统计结果
            Map<String, Integer> countDesignated;

            try {
                log.info("开始调用清单解析-上传清单接口");
                ResponseEntity<GenericDataResponse<ParserResultVo>> responseEntity =
                        parserClient.analysisExcelByFile(excelFile, PolicyTypeEnum.getByCode(policyType).getModelCode(), ReturnType.ALL, SaveType.ALL, CHANNEL_CODE);
                parseVos = checkResponse(responseEntity, errorMsgList, excelFile.getName());
                if (parseVos == null || parseVos.isEmpty()) {
                    return responseVo;
                }
                ParserResultVo data = responseEntity.getBody().getData();
                fileId = data.getFileId();
                counts = data.getCounts();
                countDesignated = data.getCountDesignated();
            } catch (Exception e) {
                String errorMsg = "调用清单解析-上传清单接口失败\n";
                log.error(errorMsg);
                errorMsgList.add(errorMsg);
                e.printStackTrace();
                return responseVo;
            }

            List<EndorseEntity> endorses = getEndorses(param.getInsuranceId());
            //查询不到数据，有可能是投保中心已经出单但是信息没有同步到edu-svc服务
            //1。投保中心查询相关信息
            if (endorses == null || endorses.isEmpty()) {
                endorses = getEndorsesByIns(param.getInsuranceId(), param.getUsercode());
            }
            if (CommonUtils.isEmpty(endorses)) {
                errorMsgList.add("未查到投保信息\n");
                return responseVo;
            }
            Map<String, EndorseEntity> entityMap = new HashMap<>();
            Set<String> cardNos = new HashSet<>();
//        Set<Byte> grades = new HashSet<>();    // 有年级字段的清单使用，没有年级的不影响
            for (EndorseEntity entity : endorses) {
                entityMap.put(entity.getCardno() + entity.getGrade() + entity.getName(), entity);
                cardNos.add(entity.getCardno() + entity.getGrade() + entity.getName());
                //            grades.add(entity.getGrade());
            }

            int endorType = param.getEndorType();
            // 学制
            String eduSystem = param.getEduSystem();

            if (StringUtils.isBlank(eduSystem) &&
                    (PolicyTypeEnum.TRAINEE_SCH.getCode() == policyType
                            || PolicyTypeEnum.STUDENT_SCH.getCode() == policyType)
                    && (EndorTypeEnum.ADD.getCode() == endorType
                    || EndorTypeEnum.PROTOCALADD.getCode() == endorType
                    || EndorTypeEnum.ADD_TEACHER.getCode() == endorType)) {
                ResponseEntity<GenericListResponse<DetailVo>> appDetailPro;
                List<DetailVo> detailVos;
                List<Map<String, Object>> extendsList;
                try {
                    appDetailPro = insClient.insuranceObjects(param.getInsuranceId());
                    detailVos = appDetailPro.getBody().getList();
                    extendsList = detailVos.get(0).getExtendsList();
                } catch (Exception e) {
                    log.error("查询标的信息失败");
                    e.printStackTrace();
                    errorMsgList.add("查询学制信息失败");
                    return responseVo;
                }
                for (Map<String, Object> fields : extendsList) {
                    if (InsuranceObjectsEnum.EDU_SYSTEM.getCode().equals(fields.get("fieldCode"))) {
                        eduSystem = (String) fields.get("fieldValues");
                    }
                }
            }

            Set<Byte> excelGrades = new TreeSet<>();
            for (EndorseParseVo parseVo : parseVos) {
                String name = parseVo.getName();
                String cardNo = parseVo.getCardNo();
                String grade = parseVo.getGrade();
                byte parseGrade = getCodeByMsg(grade);
                excelGrades.add(parseGrade);

                // 根据批单类型对比最终表和当前批单人员
                if (EndorTypeEnum.ADD.getCode() == endorType
                        || EndorTypeEnum.PROTOCALADD.getCode() == endorType
                        || EndorTypeEnum.ADD_TEACHER.getCode() == endorType) {
                    // 新增人员在投保单和历次批单的最终结果中不能存在
                    if (cardNos.contains(cardNo + parseGrade + name)) {
                        String errorMsg = name +
                                " 已在之前参保\n";
                        errorMsgList.add(errorMsg);
                    }

                    if ((PolicyTypeEnum.TRAINEE_SCH.getCode() == policyType
                            || PolicyTypeEnum.STUDENT_SCH.getCode() == policyType)) {
                        int eduSys = EduSystemEnum.getCodeByMsg(eduSystem);
                        if (parseGrade > eduSys && eduSys != 0) {
                            errorMsgList.add(name + " 所选年级与学制不相符\n");
                        }
    /*                     if (EndorTypeEnum.ADD.getCode() == endorType && !grades.contains(parseGrade)) {
                            errorMsgList.add(grade + "在投保信息中不存在\n");
                        }*/
                    }
                }
                if (EndorTypeEnum.DELETE.getCode() == endorType
                        || EndorTypeEnum.DEL_TEACHER.getCode() == endorType) {
                    // 删减人员应该在投保单或者历次批单的最终结果中存在
                    if (!cardNos.contains(cardNo + parseGrade + name)) {
                        String errorMsg = name +
                                " 在之前没有参保\n";
                        errorMsgList.add(errorMsg);
                    } else {
                        // 年级相等，年级相等校验在if中加了，此处可以去掉
                        if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                                || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
                            byte datebaseGrade = entityMap.get(cardNo + parseGrade + name).getGrade();
                            if (parseGrade != datebaseGrade) {
                                String errorMsg = name +
                                        " 在已投保信息中年级为【" +
                                        GradeEnum.getMsgByCode(datebaseGrade) +
                                        "】与清单中【" +
                                        parseVo.getGrade() +
                                        "】不相符\n";
                                errorMsgList.add(errorMsg);
                            }
                        }

                        // 附件险相等
                        if (policyType == PolicyTypeEnum.TRAINEE_ENT.getCode()) {
                            if (!isEqual(parseVo.getAdditionalInternship(), entityMap.get(cardNo + parseGrade + name).getAdditionalinternship())) {
                                String errorMsg = name +
                                        " 在已投保信息与清单中的附加险不相同\n";
                                errorMsgList.add(errorMsg);
                            }
                        } else if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()) {
                            if (!isEqual(parseVo.getAdditionalInsured(), entityMap.get(cardNo + parseGrade + name).getAdditionalinsured())
                                    || !isEqual(parseVo.getAdditionalInternship(), entityMap.get(cardNo + parseGrade + name).getAdditionalinternship())) {
                                String errorMsg = name +
                                        " 在已投保信息与清单中的附加险不相同\n";
                                errorMsgList.add(errorMsg);
                            }
                        } else if (policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
                            if (!isEqual(parseVo.getAdditionalSchool(), entityMap.get(cardNo + parseGrade + name).getAdditionalschool())) {
                                String errorMsg = name +
                                        " 在已投保信息与清单中的附加险不相同\n";
                                errorMsgList.add(errorMsg);
                            }
                        }
                    }
                }
                if (EndorTypeEnum.ADD_TEACHER.getCode() == endorType
                        || EndorTypeEnum.DEL_TEACHER.getCode() == endorType) {
                    // 新增、删除附加派遣教师的身份必须是“教师”
                    if (!StudentTeacherEnum.TEACHER.getMsg().equals(parseVo.getStudentTeacher())) {
                        String errorMsg = name +
                                " 的身份必须是【教师】\n";
                        errorMsgList.add(errorMsg);
                    }
                }

                // 控制错误信息条数
                if (errorMsgList.size() >= MSG_NUM) {
                    return responseVo;
                }
            }

            // 批加清单中出现的各年级至少一个“学生”
/*        if ((PolicyTypeEnum.TRAINEE_SCH.getCode() == policyType
                || PolicyTypeEnum.TRAINEE_ENT.getCode() == policyType)
                && EndorTypeEnum.ADD.getCode() == endorType) {
            for (Byte g : excelGrades) {
                if (policyType == PolicyTypeEnum.TRAINEE_ENT.getCode()) {
                    if (countDesignated.get("student") == 0) {
                        errorMsgList.add(GradeEnum.getMsgByCode(g) + "需至少一个【学生】\n");
                    }
                }
                if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()) {
                    String gradeKey = "grade" + g + "_student";
                    if (countDesignated.get(gradeKey) == 0) {
                        errorMsgList.add(GradeEnum.getMsgByCode(g) + "需至少一个【学生】\n");
                    }
                }
                if (policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
                    String gradeKey = "grade" + g + "_Insured";
                    if (countDesignated.get(gradeKey) == 0) {
                        errorMsgList.add(GradeEnum.getMsgByCode(g) + "需至少一个【学生】\n");
                    }
                }

            }
        }*/

            // 投保单附加险和批单附加险的对比校验
            if ((PolicyTypeEnum.TRAINEE_ENT.getCode() == policyType
                    || PolicyTypeEnum.TRAINEE_SCH.getCode() == policyType
                    || PolicyTypeEnum.STUDENT_SCH.getCode() == policyType)
                    && (EndorTypeEnum.ADD.getCode() == endorType
                    || EndorTypeEnum.PROTOCALADD.getCode() == endorType
                    || EndorTypeEnum.ADD_TEACHER.getCode() == endorType)) {
                ResponseEntity<GenericDataResponse<AppDetailProVo>> appDetailPro;
                AppDetailProVo detailVos;
                AppDetailProParam p = new AppDetailProParam();
                p.setAppCode(param.getInsuranceId());
                p.setChannelCode("edu");    // 渠道编号
                p.setUserCode(param.getUsercode());
                try {
                    appDetailPro = insClient.searchAppDetailPro(p);
                    detailVos = appDetailPro.getBody().getData();
                } catch (Exception e) {
                    log.error("查询投保单信息失败");
                    e.printStackTrace();
                    errorMsgList.add("查询投保档次限额数据信息失败");
                    return responseVo;
                }
                if (detailVos == null || CommonUtils.isEmpty(detailVos.getRiskList())) {
                    errorMsgList.add("查询投保档次限额数据信息失败");
                    return responseVo;
                }
                List<Map<String, Object>> riskList = detailVos.getRiskList();
                if (PolicyTypeEnum.TRAINEE_SCH.getCode() == policyType) {
                    boolean isInternshipAppear = false;
                    boolean isInsuredAppear = false;
                    for (Map<String, Object> risk : riskList) {

                        //FIXME 逻辑有问题（投保但选附加险，批改可以不选附加险）
                        //A、B 两款 目前前端控制批改和投保的险种保持一致的校验
                        if (RiskCodeEnum.INTERNSHIP.getCode().equals(risk.get("riskCode")) || RiskCodeEnum.INTERNSHIP_B.getCode().equals(risk.get("riskCode"))) {  // 附加实习无过失
                            isInternshipAppear = true;
                            /*List<Map<String, Object>> duty = (List<Map<String, Object>>) risk.get("dutyList");
                            Boolean isEmpty = CommonUtils.isEmpty(duty);
                            for (byte grade : excelGrades) {
                                String internshipKey = "grade" + grade + "_Internship";
                                if (countDesignated.get(internshipKey) == 0 && !isEmpty) {
                                    errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加实习无过失与投保单不一致\n");
                                }
                                if (countDesignated.get(internshipKey) != 0 && isEmpty) {
                                    errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加实习无过失与投保单不一致\n");
                                }
                            }*/
                        }
                        if (RiskCodeEnum.INSURED.getCode().equals(risk.get("riskCode"))) {  // 附加被保险人
                            isInsuredAppear = true;
                            /*List<Map<String, Object>> duty = (List<Map<String, Object>>) risk.get("dutyList");
                            Boolean isEmpty = CommonUtils.isEmpty(duty);
                            for (byte grade : excelGrades) {
                                String insuredKey = "grade" + grade + "_Insured";
                                if (countDesignated.get(insuredKey) == 0 && !isEmpty) {
                                    errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加被保险人与投保单不一致\n");
                                }
                                if (countDesignated.get(insuredKey) != 0 && isEmpty) {
                                    errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加被保险人与投保单不一致\n");
                                }
                            }*/
                        }
                    }
                    if (!isInternshipAppear) {
                        for (byte grade : excelGrades) {
                            String internshipKey = "grade" + grade + "_Internship";
                            if (countDesignated.get(internshipKey) != 0) {
                                errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加实习无过失与投保单不一致\n");
                            }
                        }
                    }
                    if (!isInsuredAppear) {
                        for (byte grade : excelGrades) {
                            String insuredKey = "grade" + grade + "_Insured";
                            if (countDesignated.get(insuredKey) != 0) {
                                errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加被保险人与投保单不一致\n");
                            }
                        }
                    }
                } else if (PolicyTypeEnum.STUDENT_SCH.getCode() == policyType) {
                    boolean isAppear = false;
                    boolean isSchoolOut = false;
                    boolean isCyb = false;
                    for (Map<String, Object> risk : riskList) {
                        if (RiskCodeEnum.SCHOOL.getCode().equals(risk.get("riskCode"))) {  // 附加校方无过失责任保险
                            isAppear = true;

                            /*List<Map<String, Object>> duty = (List<Map<String, Object>>) risk.get("dutyList");
                            Boolean isEmpty = CommonUtils.isEmpty(duty);
                            for (byte grade : excelGrades) {
                                String schoolKey = "grade" + grade + "_school";
                                if (countDesignated.get(schoolKey) == 0 && !isEmpty) {
                                    errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加校方无过失责任保险与投保单不一致\n");
                                }
                                if (countDesignated.get(schoolKey) != 0 && isEmpty) {
                                    errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加校方无过失责任保险与投保单不一致\n");
                                }
                            }*/

                        }
                        if (RiskCodeEnum.OUT_SCHOOL.getCode().equals(risk.get("riskCode"))) {  // 附加校方无过失责任保险
                            isSchoolOut = true;

                            /*List<Map<String, Object>> duty = (List<Map<String, Object>>) risk.get("dutyList");
                            Boolean isEmpty = CommonUtils.isEmpty(duty);
                            for (byte grade : excelGrades) {
                                String schoolKey = "grade" + grade + "_school";
                                if (countDesignated.get(schoolKey) == 0 && !isEmpty) {
                                    errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加校方无过失责任保险与投保单不一致\n");
                                }
                                if (countDesignated.get(schoolKey) != 0 && isEmpty) {
                                    errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加校方无过失责任保险与投保单不一致\n");
                                }
                            }*/

                        }
                        if (RiskCodeEnum.INFECTIOUS.getCode().equals(risk.get("riskCode"))) {  // 附加校方无过失责任保险
                            isCyb = true;

                            /*List<Map<String, Object>> duty = (List<Map<String, Object>>) risk.get("dutyList");
                            Boolean isEmpty = CommonUtils.isEmpty(duty);
                            for (byte grade : excelGrades) {
                                String schoolKey = "grade" + grade + "_school";
                                if (countDesignated.get(schoolKey) == 0 && !isEmpty) {
                                    errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加校方无过失责任保险与投保单不一致\n");
                                }
                                if (countDesignated.get(schoolKey) != 0 && isEmpty) {
                                    errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加校方无过失责任保险与投保单不一致\n");
                                }
                            }*/

                        }
                    }
                    if (!isAppear) {
                        for (byte grade : excelGrades) {
                            String schoolKey = "grade" + grade + "_school";
                            if (countDesignated.get(schoolKey) != 0) {
                                errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加校方无过失责任保险与投保单不一致\n");
                            }
                        }
                    }
                    if (!isSchoolOut) {
                        for (byte grade : excelGrades) {
                            String schoolKey = "grade" + grade + "_outschool";
                            if (countDesignated.get(schoolKey) != 0) {
                                errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加校方无过失责任保险与投保单不一致\n");
                            }
                        }
                    }
                    if (!isCyb) {
                        for (byte grade : excelGrades) {
                            String schoolKey = "grade" + grade + "_infectious";
                            if (countDesignated.get(schoolKey) != 0) {
                                errorMsgList.add(GradeEnum.getMsgByCode(grade) + "批改上传清单所选附加校方无过失责任保险与投保单不一致\n");
                            }
                        }
                    }
                } else if (PolicyTypeEnum.TRAINEE_ENT.getCode() == policyType) {
                    boolean isAppear = false;
                    for (Map<String, Object> risk : riskList) {
                        if (RiskCodeEnum.INTERNSHIP.getCode().equals(risk.get("riskCode"))) {  // 附加实习无过失
                            isAppear = true;
                            /*List<Map<String, Object>> duty = (List<Map<String, Object>>) risk.get("dutyList");
                            Boolean isEmpty = CommonUtils.isEmpty(duty);
                            if (countDesignated.get("Internship") == 0 && !isEmpty) {
                                errorMsgList.add("批改上传清单所选附加实习无过失与投保单不一致\n");
                            }
                            if (countDesignated.get("Internship") != 0 && isEmpty) {
                                errorMsgList.add("批改上传清单所选附加实习无过失与投保单不一致\n");
                            }*/
                        }
                    }
                    if (!isAppear && countDesignated.get("Internship") != 0) {
                        errorMsgList.add("批改上传清单所选附加实习无过失与投保单不一致\n");
                    }
                }
            }

            if (errorMsgList.size() == 0) {
                responseVo.setFileId(fileId);
                responseVo.setCounts(counts);
                responseVo.setCountDesignated(countDesignated);
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            if (redisTemplate.hasKey(PAUSE + param.getInsuranceId())) {
                redisTemplate.delete(PAUSE + param.getInsuranceId());
            }
        }
        return responseVo;
    }

    /**
     * 处理接口返回结果
     */
    private List<EndorseParseVo> checkResponse(ResponseEntity<GenericDataResponse<ParserResultVo>> responseEntity,
                                               Set<String> errorMsgList,
                                               String filename) {
        ParserResultVo data = responseEntity.getBody().getData();
        if (data == null) {
            String errorMsg = filename + " 调用清单解析-上传清单接口失败\n";
            if (responseEntity.getBody().getMsg() != null) {
                errorMsg = responseEntity.getBody().getMsg() + "\n";
            }
            log.error(errorMsg);
            errorMsgList.add(errorMsg);
            return null;
        }

        // 接口正常返回错误信息，添加到errorMsgList
        List<Map<String, String>> errorMessages = data.getErrorMessages();
        if (!CommonUtils.isEmpty(errorMessages)) {
            for (Map<String, String> errorMessage : errorMessages) {
                String row = errorMessage.get("row");
                String column = errorMessage.get("column");
                String error = errorMessage.get("errorMessage");
                StringBuilder errorMsg = new StringBuilder();
                errorMsg.append(filename).append(":");
                if (Integer.valueOf(row) != 0) {
                    errorMsg.append("第").append(row).append("行");
                }
                if (Integer.valueOf(column) != 0) {
                    errorMsg.append("第").append(column).append("列");
                }
                errorMsg.append(error).append("\n");
                log.error(String.valueOf(errorMsg));
                errorMsgList.add(String.valueOf(errorMsg));
            }
            return null;
        }
        return data.getDatas();
    }

    /**
     * 查询最终表证件号
     */
    private Map<String, EndorseEntity> getCardNos(String insuranceId) {
        List<EndorseEntity> endorses = getEndorses(insuranceId);
        if (CommonUtils.isEmpty(endorses)) {
            log.error("未查到投保信息\n");
            return new HashMap<>();
        }
        Map<String, EndorseEntity> entityMap = new HashMap<>();
        for (EndorseEntity entity : endorses) {
            entityMap.put(entity.getCardno() + entity.getGrade() + entity.getName(), entity);
        }
        return entityMap;
    }

    /**
     * 查询最终表证件号
     */
    private Map<String, EndorseEntity> getCardNosV2(String userCode, String insuranceId) {
        List<EndorseEntity> endorses = getEndorses(insuranceId);
        Map<String, EndorseEntity> entityMap = new HashMap<>();
        if (CommonUtils.isEmpty(endorses)) {
            //
            endorses = getEndorsesByIns(insuranceId, userCode);
        }
        if (endorses == null || endorses.isEmpty()) {
            log.error("未查到投保信息\n");
            return entityMap;
        }
        for (EndorseEntity entity : endorses) {
            entityMap.put(entity.getCardno() + entity.getGrade() + entity.getName(), entity);
        }
        return entityMap;
    }

    @Override
    public EndorseResponseVo checkReplaceEndorse(String insuranceId, Integer policyType,
                                                 MultipartFile replaceExcelFile, String originFileId) {
        // 错误信息列表
        Set<String> errorMsgList = new HashSet<>();
        // 返回对象
        EndorseResponseVo responseVo = new EndorseResponseVo();
        responseVo.setErrorMsgList(errorMsgList);
        // excel清单解析的替换（新）对象列表
        List<EndorseParseVo> replaceEntities;
        // 上传的文件id
        String replaceFileId;
        // 上传人数
        Integer counts;
        // 统计结果
        Map<String, Integer> countDesignated;

        try {
            log.info("开始调用清单解析-上传清单接口");
            ResponseEntity<GenericDataResponse<ParserResultVo>> replaceVo =
                    parserClient.analysisExcelByFile(replaceExcelFile, PolicyTypeEnum.getByCode(policyType).getModelCode(), ALL, SaveType.ALL, CHANNEL_CODE);
            replaceEntities = checkResponse(replaceVo, errorMsgList, replaceExcelFile.getName());
            if (replaceEntities == null || replaceEntities.isEmpty()) {
                return responseVo;
            }
            ParserResultVo data = replaceVo.getBody().getData();
            replaceFileId = data.getFileId();
            counts = data.getCounts();
            countDesignated = data.getCountDesignated();
        } catch (Exception e) {
            String errorMsg = "调用清单解析-上传清单接口失败\n";
            log.error(errorMsg);
            errorMsgList.add(errorMsg);
            e.printStackTrace();
            return responseVo;
        }

        Map<String, EndorseEntity> entityMap = getCardNos(insuranceId);
        Set<String> cardNos = entityMap.keySet();
        // 开始批单时在最终表必有投保信息
        if (CommonUtils.isEmpty(cardNos)) {
            errorMsgList.add("未查到投保信息\n");
            return responseVo;
        }

        // excel清单解析的被替换（原）对象列表
        List<ExcelFileDetailVo> originEntities;
        try {
            ResponseEntity<GenericListResponse<ExcelFileDetailVo>> responseEntity =
                    parserClient.excelDetailList(originFileId, null, 1, Integer.MAX_VALUE);
            originEntities = responseEntity.getBody().getList();
        } catch (Exception e) {
            String errorMsg = "调用清单解析-清单详细列表接口 查询被替换清单 失败\n";
            log.error(errorMsg);
            e.printStackTrace();
            errorMsgList.add(errorMsg);
            return responseVo;
        }
        List<EndorseParseVo> originParseVo = parseContent(originEntities);

        // 学校实责and校责：int[0]学生数
        // 学校实责：int[1]教师数，int[2]附加被保险人数，int[3]附加实习无过失数，int[4]both 附加被保险人and 附加实习无过失
        // 校责：int[5]校方无过失 int[6]传染病  int[7]校方+校外  int[8]校方+传染病  int[9]校方+校外+传染病
        Map<GradeEnum, int[]> replaceNumMap = new HashMap<>();
        Map<GradeEnum, int[]> originNumMap = new HashMap<>();
        if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
            for (GradeEnum gradeEnum : GradeEnum.values()) {
                replaceNumMap.put(gradeEnum, new int[10]);
            }
            for (GradeEnum gradeEnum : GradeEnum.values()) {
                originNumMap.put(gradeEnum, new int[10]);
            }
        }

        // 企业实责：int[0]学生数，int[1]教师数，int[2]附加实习无过失数
        int[] replaceTraineeEnt = new int[3];
        int[] originTraineeEnt = new int[3];

        for (EndorseParseVo replaceParse : replaceEntities) {
            String name = replaceParse.getName();
            String cardNo = replaceParse.getCardNo();
            String grade = replaceParse.getGrade();
            byte parseGrade = getCodeByMsg(grade);
            //FIXME 替换人员在投保单和历次批单的最终结果中不能存在  或者  替换人员批单的最终结果 只能为批减时
            if (cardNos.contains(cardNo + parseGrade + name)) {
                String errorMsg = "替换清单中 " +
                        name +
                        " 已在之前参保\n";
                errorMsgList.add(errorMsg);
            }

            // 学校实责、校责
            if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                    || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
                GradeEnum gradeEnum = GradeEnum.getByMsg(grade);
                if (gradeEnum != null) {
                    setSchoolNum(replaceParse, replaceNumMap, gradeEnum, policyType);
                } else {
                    String errorMsg = "替换清单中有不能识别的年级：" +
                            grade +
                            "\n";
                    errorMsgList.add(errorMsg);
                }
            }
            // 企业实责
            if (policyType == PolicyTypeEnum.TRAINEE_ENT.getCode()) {
                setTraineeEntNum(replaceTraineeEnt, replaceParse);
            }

            // 控制错误信息条数
            if (errorMsgList.size() >= MSG_NUM) {
                return responseVo;
            }
        }

        // 学校实责、校责
        if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
            for (EndorseParseVo originParse : originParseVo) {
                GradeEnum gradeEnum = GradeEnum.getByMsg(originParse.getGrade());
                if (gradeEnum != null) {
                    setSchoolNum(originParse, originNumMap, gradeEnum, policyType);
                }
            }
        }
        // 企业实责
        if (policyType == PolicyTypeEnum.TRAINEE_ENT.getCode()) {
            originParseVo.forEach(o -> setTraineeEntNum(originTraineeEnt, o));
        }

        if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
            for (GradeEnum gradeEnum : GradeEnum.values()) {
                if (replaceNumMap.get(gradeEnum)[0] != originNumMap.get(gradeEnum)[0]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 学生 人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[1] != originNumMap.get(gradeEnum)[1]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 教师 人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[2] != originNumMap.get(gradeEnum)[2]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加被保险人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[3] != originNumMap.get(gradeEnum)[3]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加实习无过失人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[4] != originNumMap.get(gradeEnum)[4]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 同时投保附加被保险人和实习无过失两个附加险的人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[5] != originNumMap.get(gradeEnum)[5]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加校方无过失的人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[6] != originNumMap.get(gradeEnum)[6]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加传染病人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[7] != originNumMap.get(gradeEnum)[7]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加校方无过失和附加校外的人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[8] != originNumMap.get(gradeEnum)[8]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加校方无过失和附加传染病的人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[9] != originNumMap.get(gradeEnum)[9]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加校方无过失和附加校外和附加传染病的人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
            }
        }

        if (policyType == PolicyTypeEnum.TRAINEE_ENT.getCode()) {
            if (replaceTraineeEnt[0] != originTraineeEnt[0]) {
                String errorMsg = "替换清单和被替换清单中 学生 人数不相等\n";
                errorMsgList.add(errorMsg);
            }
            if (replaceTraineeEnt[1] != originTraineeEnt[1]) {
                String errorMsg = "替换清单和被替换清单中 教师 人数不相等\n";
                errorMsgList.add(errorMsg);
            }
            if (replaceTraineeEnt[2] != originTraineeEnt[2]) {
                String errorMsg = "替换清单和被替换清单中 附加实习无过失 人数不相等\n";
                errorMsgList.add(errorMsg);
            }
        }

        if (policyType == PolicyTypeEnum.YN_JZG.getCode()
                || policyType == PolicyTypeEnum.YN_XF.getCode()
                || policyType == PolicyTypeEnum.YN_ZH.getCode()
                || policyType == PolicyTypeEnum.SD_XF.getCode()
                || policyType == PolicyTypeEnum.SD_LY.getCode()
                || policyType == PolicyTypeEnum.SP_XZ.getCode()) {
            if (replaceEntities.size() != originParseVo.size()) {
                String errorMsg = "替换清单和被替换清单中人数不相等\n";
                errorMsgList.add(errorMsg);
            }
        }

        if (errorMsgList.size() == 0) {
            responseVo.setFileId(replaceFileId);
            responseVo.setCounts(counts);
            responseVo.setCountDesignated(countDesignated);
        }
        return responseVo;
    }

    @Override
    public EndorseResponseVo checkOriginEndorse(String insuranceId, Integer policyType,
                                                MultipartFile originExcelFile) {
        // 错误信息列表
        Set<String> errorMsgList = new HashSet<>();
        // 返回对象
        EndorseResponseVo responseVo = new EndorseResponseVo();
        responseVo.setErrorMsgList(errorMsgList);
        // excel清单解析的被替换（原）对象列表
        List<EndorseParseVo> originEntities;
        // 上传的文件id
        String originFileId;
        // 上传人数
        Integer counts;
        // 统计结果
        Map<String, Integer> countDesignated;

        try {
            log.info("开始调用清单解析-上传清单接口");
            ResponseEntity<GenericDataResponse<ParserResultVo>> originVo =
                    parserClient.analysisExcelByFile(originExcelFile, PolicyTypeEnum.getByCode(policyType).getModelCode(), ALL, SaveType.ALL, CHANNEL_CODE);
            originEntities = checkResponse(originVo, errorMsgList, originExcelFile.getName());
            if (originEntities == null || originEntities.isEmpty()) {
                return responseVo;
            }
            ParserResultVo data = originVo.getBody().getData();
            originFileId = data.getFileId();
            counts = data.getCounts();
            countDesignated = data.getCountDesignated();
        } catch (Exception e) {
            String errorMsg = "调用清单解析-上传清单接口失败\n";
            log.error(errorMsg);
            errorMsgList.add(errorMsg);
            e.printStackTrace();
            return responseVo;
        }

        Map<String, EndorseEntity> entityMap = getCardNos(insuranceId);
        Set<String> cardNos = entityMap.keySet();
        // 开始批单时在最终表必有投保信息
        if (CommonUtils.isEmpty(cardNos)) {
            errorMsgList.add("未查到投保信息\n");
            return responseVo;
        }

        for (EndorseParseVo originParse : originEntities) {
            String name = originParse.getName();
            String cardNo = originParse.getCardNo();
            String grade = originParse.getGrade();
            byte parseGrade = getCodeByMsg(grade);
            // 被替换人员应该在投保单或者历次批单的最终结果中存在
            if (!cardNos.contains(cardNo + parseGrade + name)) {
                String errorMsg = "被替换清单中 " +
                        name +
                        " 在之前没有参保\n";
                errorMsgList.add(errorMsg);
            } else {
                if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                        || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
                    byte datebaseGrade = entityMap.get(cardNo + parseGrade + name).getGrade();
                    if (parseGrade != datebaseGrade) {
                        String errorMsg = name +
                                " 在已投保信息中年级为【" +
                                GradeEnum.getMsgByCode(datebaseGrade) +
                                "】与清单中【" +
                                grade +
                                "】不相符\n";
                        errorMsgList.add(errorMsg);
                    }
                }
            }

            // 学校实责、校责
            if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                    || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
                GradeEnum gradeEnum = GradeEnum.getByMsg(originParse.getGrade());
                if (gradeEnum == null) {
                    String errorMsg = "被替换清单中有不能识别的年级：" +
                            grade +
                            "\n";
                    errorMsgList.add(errorMsg);
                }
            }

            // 控制错误信息条数
            if (errorMsgList.size() >= MSG_NUM) {
                return responseVo;
            }
        }

        if (errorMsgList.size() == 0) {
            responseVo.setFileId(originFileId);
            responseVo.setCounts(counts);
            responseVo.setCountDesignated(countDesignated);
        }
        return responseVo;
    }

    @Override
    public EndorseResponseVo checkReplaceEndorseV2(String userCode, String insuranceId, Integer policyType,
                                                   MultipartFile replaceExcelFile, String originFileId) {
        // 错误信息列表
        Set<String> errorMsgList = new HashSet<>();
        // 返回对象
        EndorseResponseVo responseVo = new EndorseResponseVo();
        responseVo.setErrorMsgList(errorMsgList);
        // excel清单解析的替换（新）对象列表
        List<EndorseParseVo> replaceEntities;
        // 上传的文件id
        String replaceFileId;
        // 上传人数
        Integer counts;
        // 统计结果
        Map<String, Integer> countDesignated;

        try {
            log.info("开始调用清单解析-上传清单接口");
            ResponseEntity<GenericDataResponse<ParserResultVo>> replaceVo =
                    parserClient.analysisExcelByFile(replaceExcelFile, PolicyTypeEnum.getByCode(policyType).getModelCode(), ALL, SaveType.ALL, CHANNEL_CODE);
            replaceEntities = checkResponse(replaceVo, errorMsgList, replaceExcelFile.getName());
            if (replaceEntities == null || replaceEntities.isEmpty()) {
                return responseVo;
            }
            ParserResultVo data = replaceVo.getBody().getData();
            replaceFileId = data.getFileId();
            counts = data.getCounts();
            countDesignated = data.getCountDesignated();
        } catch (Exception e) {
            String errorMsg = "调用清单解析-上传清单接口失败\n";
            log.error(errorMsg);
            errorMsgList.add(errorMsg);
            e.printStackTrace();
            return responseVo;
        }

        Map<String, EndorseEntity> entityMap = getCardNosV2(userCode, insuranceId);
        Set<String> cardNos = entityMap.keySet();
        // 开始批单时在最终表必有投保信息
        if (CommonUtils.isEmpty(cardNos)) {
            errorMsgList.add("未查到投保信息\n");
            return responseVo;
        }

        // excel清单解析的被替换（原）对象列表
        List<ExcelFileDetailVo> originEntities;
        try {
            ResponseEntity<GenericListResponse<ExcelFileDetailVo>> responseEntity =
                    parserClient.excelDetailList(originFileId, null, 1, Integer.MAX_VALUE);
            originEntities = responseEntity.getBody().getList();
        } catch (Exception e) {
            String errorMsg = "调用清单解析-清单详细列表接口 查询被替换清单 失败\n";
            log.error(errorMsg);
            e.printStackTrace();
            errorMsgList.add(errorMsg);
            return responseVo;
        }
        List<EndorseParseVo> originParseVo = parseContent(originEntities);

        // 学校实责and校责：int[0]学生数
        // 学校实责：int[1]教师数，int[2]附加被保险人数，int[3]附加实习无过失数，int[4]both 附加被保险人and 附加实习无过失
        // 校责：int[5]校方无过失 int[6]传染病  int[7]校方+校外  int[8]校方+传染病  int[9]校方+校外+传染病
        Map<GradeEnum, int[]> replaceNumMap = new HashMap<>();
        Map<GradeEnum, int[]> originNumMap = new HashMap<>();
        if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
            for (GradeEnum gradeEnum : GradeEnum.values()) {
                replaceNumMap.put(gradeEnum, new int[10]);
            }
            for (GradeEnum gradeEnum : GradeEnum.values()) {
                originNumMap.put(gradeEnum, new int[10]);
            }
        }

        // 企业实责：int[0]学生数，int[1]教师数，int[2]附加实习无过失数
        int[] replaceTraineeEnt = new int[3];
        int[] originTraineeEnt = new int[3];

        for (EndorseParseVo replaceParse : replaceEntities) {
            String name = replaceParse.getName();
            String cardNo = replaceParse.getCardNo();
            String grade = replaceParse.getGrade();
            byte parseGrade = getCodeByMsg(grade);
            //FIXME 替换人员在投保单和历次批单的最终结果中不能存在  或者  替换人员批单的最终结果 只能为批减时
            if (cardNos.contains(cardNo + parseGrade + name)) {
                String errorMsg = "替换清单中 " +
                        name +
                        " 已在之前参保\n";
                errorMsgList.add(errorMsg);
            }

            // 学校实责、校责
            if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                    || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
                GradeEnum gradeEnum = GradeEnum.getByMsg(grade);
                if (gradeEnum != null) {
                    setSchoolNum(replaceParse, replaceNumMap, gradeEnum, policyType);
                } else {
                    String errorMsg = "替换清单中有不能识别的年级：" +
                            grade +
                            "\n";
                    errorMsgList.add(errorMsg);
                }
            }
            // 企业实责
            if (policyType == PolicyTypeEnum.TRAINEE_ENT.getCode()) {
                setTraineeEntNum(replaceTraineeEnt, replaceParse);
            }

            // 控制错误信息条数
            if (errorMsgList.size() >= MSG_NUM) {
                return responseVo;
            }
        }

        // 学校实责、校责
        if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
            for (EndorseParseVo originParse : originParseVo) {
                GradeEnum gradeEnum = GradeEnum.getByMsg(originParse.getGrade());
                if (gradeEnum != null) {
                    setSchoolNum(originParse, originNumMap, gradeEnum, policyType);
                }
            }
        }
        // 企业实责
        if (policyType == PolicyTypeEnum.TRAINEE_ENT.getCode()) {
            originParseVo.forEach(o -> setTraineeEntNum(originTraineeEnt, o));
        }

        if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
            for (GradeEnum gradeEnum : GradeEnum.values()) {
                if (replaceNumMap.get(gradeEnum)[0] != originNumMap.get(gradeEnum)[0]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 学生 人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[1] != originNumMap.get(gradeEnum)[1]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 教师 人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[2] != originNumMap.get(gradeEnum)[2]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加被保险人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[3] != originNumMap.get(gradeEnum)[3]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加实习无过失人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[4] != originNumMap.get(gradeEnum)[4]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 同时投保附加被保险人和实习无过失两个附加险的人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[5] != originNumMap.get(gradeEnum)[5]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加校方无过失的人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[6] != originNumMap.get(gradeEnum)[6]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加传染病人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[7] != originNumMap.get(gradeEnum)[7]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加校方无过失和附加校外的人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[8] != originNumMap.get(gradeEnum)[8]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加校方无过失和附加传染病的人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
                if (replaceNumMap.get(gradeEnum)[9] != originNumMap.get(gradeEnum)[9]) {
                    String errorMsg = "替换清单和被替换清单中 " +
                            gradeEnum.getMsg() +
                            " 的 附加校方无过失和附加校外和附加传染病的人数不相等\n";
                    errorMsgList.add(errorMsg);
                }
            }
        }

        if (policyType == PolicyTypeEnum.TRAINEE_ENT.getCode()) {
            if (replaceTraineeEnt[0] != originTraineeEnt[0]) {
                String errorMsg = "替换清单和被替换清单中 学生 人数不相等\n";
                errorMsgList.add(errorMsg);
            }
            if (replaceTraineeEnt[1] != originTraineeEnt[1]) {
                String errorMsg = "替换清单和被替换清单中 教师 人数不相等\n";
                errorMsgList.add(errorMsg);
            }
            if (replaceTraineeEnt[2] != originTraineeEnt[2]) {
                String errorMsg = "替换清单和被替换清单中 附加实习无过失 人数不相等\n";
                errorMsgList.add(errorMsg);
            }
        }

        if (policyType == PolicyTypeEnum.YN_JZG.getCode()
                || policyType == PolicyTypeEnum.YN_XF.getCode()
                || policyType == PolicyTypeEnum.YN_ZH.getCode()
                || policyType == PolicyTypeEnum.SD_LY.getCode()
                || policyType == PolicyTypeEnum.SD_XF.getCode()
                || policyType == PolicyTypeEnum.SP_XZ.getCode()) {
            if (replaceEntities.size() != originParseVo.size()) {
                String errorMsg = "替换清单和被替换清单中人数不相等\n";
                errorMsgList.add(errorMsg);
            }
        }

        if (errorMsgList.size() == 0) {
            responseVo.setFileId(replaceFileId);
            responseVo.setCounts(counts);
            responseVo.setCountDesignated(countDesignated);
        }
        return responseVo;
    }

    @Override
    public EndorseResponseVo checkOriginEndorseV2(String userCode, String insuranceId, Integer policyType,
                                                  MultipartFile originExcelFile) {
        // 错误信息列表
        Set<String> errorMsgList = new HashSet<>();
        // 返回对象
        EndorseResponseVo responseVo = new EndorseResponseVo();
        responseVo.setErrorMsgList(errorMsgList);
        // excel清单解析的被替换（原）对象列表
        List<EndorseParseVo> originEntities;
        // 上传的文件id
        String originFileId;
        // 上传人数
        Integer counts;
        // 统计结果
        Map<String, Integer> countDesignated;

        try {
            log.info("开始调用清单解析-上传清单接口");
            ResponseEntity<GenericDataResponse<ParserResultVo>> originVo =
                    parserClient.analysisExcelByFile(originExcelFile, PolicyTypeEnum.getByCode(policyType).getModelCode(), ALL, SaveType.ALL, CHANNEL_CODE);
            originEntities = checkResponse(originVo, errorMsgList, originExcelFile.getName());
            if (originEntities == null || originEntities.isEmpty()) {
                return responseVo;
            }
            ParserResultVo data = originVo.getBody().getData();
            originFileId = data.getFileId();
            counts = data.getCounts();
            countDesignated = data.getCountDesignated();
        } catch (Exception e) {
            String errorMsg = "调用清单解析-上传清单接口失败\n";
            log.error(errorMsg);
            errorMsgList.add(errorMsg);
            e.printStackTrace();
            return responseVo;
        }

        Map<String, EndorseEntity> entityMap = getCardNosV2(userCode, insuranceId);
        Set<String> cardNos = entityMap.keySet();
        // 开始批单时在最终表必有投保信息
        if (CommonUtils.isEmpty(cardNos)) {
            errorMsgList.add("未查到投保信息\n");
            return responseVo;
        }

        for (EndorseParseVo originParse : originEntities) {
            String name = originParse.getName();
            String cardNo = originParse.getCardNo();
            String grade = originParse.getGrade();
            byte parseGrade = getCodeByMsg(grade);
            // 被替换人员应该在投保单或者历次批单的最终结果中存在
            if (!cardNos.contains(cardNo + parseGrade + name)) {
                String errorMsg = "被替换清单中 " +
                        name +
                        " 在之前没有参保\n";
                errorMsgList.add(errorMsg);
            } else {
                if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                        || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
                    byte datebaseGrade = entityMap.get(cardNo + parseGrade + name).getGrade();
                    if (parseGrade != datebaseGrade) {
                        String errorMsg = name +
                                " 在已投保信息中年级为【" +
                                GradeEnum.getMsgByCode(datebaseGrade) +
                                "】与清单中【" +
                                grade +
                                "】不相符\n";
                        errorMsgList.add(errorMsg);
                    }
                }
            }

            // 学校实责、校责
            if (policyType == PolicyTypeEnum.TRAINEE_SCH.getCode()
                    || policyType == PolicyTypeEnum.STUDENT_SCH.getCode()) {
                GradeEnum gradeEnum = GradeEnum.getByMsg(originParse.getGrade());
                if (gradeEnum == null) {
                    String errorMsg = "被替换清单中有不能识别的年级：" +
                            grade +
                            "\n";
                    errorMsgList.add(errorMsg);
                }
            }

            // 控制错误信息条数
            if (errorMsgList.size() >= MSG_NUM) {
                return responseVo;
            }
        }

        if (errorMsgList.size() == 0) {
            responseVo.setFileId(originFileId);
            responseVo.setCounts(counts);
            responseVo.setCountDesignated(countDesignated);
        }
        return responseVo;
    }

    /**
     * 统计企业实责各项数值
     */
    private void setTraineeEntNum(int[] traineeEntNum, EndorseParseVo endorseParseVo) {
        // 学生人数+1
        if (StudentTeacherEnum.STUDENT.getMsg().equals(endorseParseVo.getStudentTeacher())) {
            traineeEntNum[0]++;
        }
        // 教师人数+1
        if (StudentTeacherEnum.TEACHER.getMsg().equals(endorseParseVo.getStudentTeacher())) {
            traineeEntNum[1]++;
        }
        // 附加实习无过失+1
        if (AdditionalInternshipEnum.YES.getMsg().equals(endorseParseVo.getAdditionalInternship())) {
            traineeEntNum[2]++;
        }
    }

    /**
     * 统计校责、学校实责各项数值
     */
    private void setSchoolNum(EndorseParseVo replaceParse, Map<GradeEnum, int[]> numMap, GradeEnum gradeEnum, int policyType) {
        // 校责，则学生人数+1；学校实责，并且身份是学生，则学生人数+1
        if (PolicyTypeEnum.STUDENT_SCH.getCode() == policyType
                || StudentTeacherEnum.STUDENT.getMsg().equals(replaceParse.getStudentTeacher())) {
            numMap.get(gradeEnum)[0]++;
        }
        // 学校实责，并且身份是教师，则教师人数+1
        if (PolicyTypeEnum.TRAINEE_SCH.getCode() == policyType
                && StudentTeacherEnum.TEACHER.getMsg().equals(replaceParse.getStudentTeacher())) {
            numMap.get(gradeEnum)[1]++;
        }
        // 学校实责，附加被保险人
        boolean AdditionalInsuredFlag = AdditionalInsuredEnum.YES.getMsg().equals(replaceParse.getAdditionalInsured());
        if (AdditionalInsuredFlag) {
            numMap.get(gradeEnum)[2]++;
        }
        // 学校实责，附加实习无过失
        boolean AdditionalInternshipFlag = AdditionalInternshipEnum.YES.getMsg().equals(replaceParse.getAdditionalInternship());
        if (AdditionalInternshipFlag) {
            numMap.get(gradeEnum)[3]++;
        }
        // 学校实责，同时投保附加被保险人和实习无过失
        if (AdditionalInsuredFlag && AdditionalInternshipFlag) {
            numMap.get(gradeEnum)[4]++;
        }

        //int[5]校方无过失 int[6]传染病  int[7]校方+校外  int[8]校方+传染病  int[9]校方+校外+传染病
        // 校责，附加校方无过失
        if (AdditionalSchoolEnum.YES.getMsg().equals(replaceParse.getAdditionalSchool())) {
            numMap.get(gradeEnum)[5]++;
        }
        //附加传染病
        if (AdditionalSchoolEnum.YES.getMsg().equals(replaceParse.getAdditionalSchool())) {
            numMap.get(gradeEnum)[6]++;
        }
        //校责，附加校方+校外
        if (AdditionalSchoolEnum.YES.getMsg().equals(replaceParse.getAdditionalSchool())) {
            numMap.get(gradeEnum)[7]++;
        }
        //校责，附加校方+附加传染病
        if (AdditionalSchoolEnum.YES.getMsg().equals(replaceParse.getAdditionalSchool())) {
            numMap.get(gradeEnum)[8]++;
        }
        //校责，附加校方+校外+附加传染病
        if (AdditionalSchoolEnum.YES.getMsg().equals(replaceParse.getAdditionalSchool())) {
            numMap.get(gradeEnum)[9]++;
        }
    }

    @Override
    @Transactional
    public int saveEndorse(SaveEndorseParam param) {
        Integer endorType = param.getEndorType();
        int resultNum = 0;

        if (EndorTypeEnum.SURRENDER.getCode() == endorType) {
            resultNum += updateEndorses(param.getInsuranceId(), null, null,
                    StatusEnum.DELETE.getCode(), param.getUsercode());
            return resultNum;
        } else if (EndorTypeEnum.ALL_ADD_INSURANT.getCode() == endorType) {
            Weekend<EndorseEntity> weekend = new Weekend<>(EndorseEntity.class);
            WeekendCriteria<EndorseEntity, Object> criteria = weekend.weekendCriteria();
            criteria.andEqualTo(INSURANCE_ID, param.getInsuranceId());
            criteria.andEqualTo(IS_DELETED, false);
            // 过滤状态为 已删除 和 已被替换 的数据
            criteria.andNotEqualTo(STATUS, StatusEnum.DELETE.getCode());
            criteria.andNotEqualTo(STATUS, StatusEnum.ORIGIN.getCode());
            // 过滤已经选了附加被保险人的数据
            criteria.andNotEqualTo(ADDITIONAL_INSURED, AdditionalInsuredEnum.YES.getCode());

            List<EndorseEntity> endorseEntities = endorseMapper.selectByExample(weekend);
            if (CommonUtils.isEmpty(endorseEntities)) {
                log.error("投保单号：" + param.getInsuranceId() + "未查询到需要添加附加被保险人的数据");
                return -1;
            }
            for (EndorseEntity e : endorseEntities) {
                e.setUpdateBy(param.getUsercode());
                e.setAdditionalinsured(AdditionalInsuredEnum.YES.getCode());
                resultNum += endorseMapper.updateByPrimaryKeySelective(e);
            }
            return resultNum;
        } else if (EndorTypeEnum.ALL_DEL_INSURANT.getCode() == endorType) {
            Weekend<EndorseEntity> weekend = new Weekend<>(EndorseEntity.class);
            WeekendCriteria<EndorseEntity, Object> criteria = weekend.weekendCriteria();
            criteria.andEqualTo(INSURANCE_ID, param.getInsuranceId());
            criteria.andEqualTo(IS_DELETED, false);
            // 过滤状态为 已删除 和 已被替换 的数据
            criteria.andNotEqualTo(STATUS, StatusEnum.DELETE.getCode());
            criteria.andNotEqualTo(STATUS, StatusEnum.ORIGIN.getCode());
            // 过滤已经没选附加被保险人的数据
            criteria.andNotEqualTo(ADDITIONAL_INSURED, AdditionalInsuredEnum.NO.getCode());

            List<EndorseEntity> endorseEntities = endorseMapper.selectByExample(weekend);
            if (CommonUtils.isEmpty(endorseEntities)) {
                log.error("投保单号：" + param.getInsuranceId() + "未查询到可以删除附加被保险人的数据");
                return -1;
            }
            for (EndorseEntity e : endorseEntities) {
                e.setUpdateBy(param.getUsercode());
                e.setAdditionalinsured(AdditionalInsuredEnum.NO.getCode());
                resultNum += endorseMapper.updateByPrimaryKeySelective(e);
            }
            return resultNum;
        }

        // 分页辅助参数
        int prevPn = 0;
        int pn = 1;

        if (EndorTypeEnum.REPLACE.getCode() == endorType) {
            String replaceFileId = param.getFileId();
            String originFileId = param.getOriginFileId();
            List<ExcelFileDetailVo> replaceList;
            List<ExcelFileDetailVo> originList;

            while (pn > prevPn) {
                try {
                    ResponseEntity<GenericListResponse<ExcelFileDetailVo>> excelDetailVo =
                            parserClient.excelDetailList(replaceFileId, null, pn, PS);
                    replaceList = excelDetailVo.getBody().getList();
                } catch (Exception e) {
                    log.error("调用清单解析-清单详细列表接口 查询替换清单 失败");
                    e.printStackTrace();
                    return -1;
                }
                try {
                    ResponseEntity<GenericListResponse<ExcelFileDetailVo>> excelDetailVo =
                            parserClient.excelDetailList(originFileId, null, pn, PS);
                    originList = excelDetailVo.getBody().getList();
                    prevPn++;
                    // 查询到的数量小于分页大小，或者总查询数不小于记录总数，则不再查询
                    if (originList.size() == PS && (pn * PS < excelDetailVo.getBody().getTotal())) {
                        pn++;
                    }
                } catch (Exception e) {
                    log.error("调用清单解析-清单详细列表接口 查询被替换清单 失败");
                    e.printStackTrace();
                    return -1;
                }
                if (CommonUtils.isEmpty(replaceList) || CommonUtils.isEmpty(originList)) {
                    log.error("没有查询到清单数据");
                    return -1;
                }
                // 更新原来数据状态为被替换
                for (ExcelFileDetailVo vo : originList) {
                    resultNum += updateEndorses(param.getInsuranceId(), null, vo.getField2(),
                            StatusEnum.ORIGIN.getCode(), param.getUsercode());
                }
                // 插入替换的数据
                resultNum += insertEndorses(replaceList, param.getInsuranceId(), replaceFileId, StatusEnum.REPLACE.getCode(), param.getUsercode());
            }
            return resultNum;
        } else {
            // 批加减、协议批加、批加减教师
            String fileId = param.getFileId();
            List<ExcelFileDetailVo> detailList;

            while (pn > prevPn) {
                try {
                    ResponseEntity<GenericListResponse<ExcelFileDetailVo>> excelDetailVo =
                            parserClient.excelDetailList(fileId, null, pn, PS);
                    detailList = excelDetailVo.getBody().getList();
                    prevPn++;
                    if (detailList.size() == PS && (pn * PS < excelDetailVo.getBody().getTotal())) {
                        pn++;
                    }
                } catch (Exception e) {
                    log.error("调用清单解析-清单详细列表接口失败");
                    e.printStackTrace();
                    return -1;
                }
                if (CommonUtils.isEmpty(detailList)) {
                    log.error("没有查询到清单数据");
                    return -1;
                }

                if (EndorTypeEnum.ADD.getCode() == endorType
                        || EndorTypeEnum.PROTOCALADD.getCode() == endorType
                        || EndorTypeEnum.ADD_TEACHER.getCode() == endorType) {
                    resultNum += insertEndorses(detailList, param.getInsuranceId(), fileId, StatusEnum.INSERT.getCode(), param.getUsercode());
                }
                if (EndorTypeEnum.DELETE.getCode() == endorType
                        || EndorTypeEnum.DEL_TEACHER.getCode() == endorType) {
                    for (ExcelFileDetailVo vo : detailList) {
                        resultNum += updateEndorses(param.getInsuranceId(), null, vo.getField2(),
                                StatusEnum.DELETE.getCode(), param.getUsercode());
                    }
                }
            }

            return resultNum;
        }
    }

    @Override
    @Transactional
    public int saveInsurance(List<String> appCodes, String usercode, Boolean policyTypeFlag, Boolean exceptionFlag) {
        int resultNum = 0;
        String findCode = "EDU_JOB_Insurance" + System.currentTimeMillis();

        for (String appCode : appCodes) {

            log.info("{} 查询清单文件 投保单号:{}, policyTypeFlag:{}", findCode, appCode, policyTypeFlag);

            if (policyTypeFlag) {
                // 实责、校责
                ResponseEntity<GenericListResponse<DetailVo>> appDetailPro;
                List<DetailVo> detailVos;
                try {
                    appDetailPro = insClient.insuranceObjects(appCode);
                    log.info("{}查询投保单详细信息 投保单号:{}, 返回结果:{}", findCode, appCode, JsonUtil.object2Json(appDetailPro));
                    detailVos = appDetailPro.getBody().getList();
                } catch (Exception e) {
                    log.error("{}查询投保单详细信息失败 投保单号:{}", findCode, appCode);
                    return -1;
                }
                for (DetailVo detailVo : detailVos) {
                    String fileId = null;
                    String grade = null;

                    List<Map<String, Object>> extendsList = detailVo.getExtendsList();
                    for (Map<String, Object> fields : extendsList) {
                        if (fileId != null && grade != null) {
                            break;
                        }
                        if (InsuranceObjectsEnum.FILE_ID.getCode().equals(fields.get("fieldCode"))) {
                            fileId = (String) fields.get("fieldValues");
                            log.info("{}实责校责 清单附件ID:{}", findCode, fileId);
                        }
                        if (InsuranceObjectsEnum.GRADE.getCode().equals(fields.get("fieldCode"))) {
                            String englishGrade = (String) fields.get("fieldValues");
                            grade = GradeEnum.getMsgByEnCode(englishGrade);
                            log.info("{}实责校责 grade:{}", findCode, grade);
                        }
                    }
                    if (fileId == null) {
                        log.info("{}实责校责 清单附件ID为空", findCode);
                        continue;
                    }

                    int prevPn = 0;
                    int pn = 1;
                    while (pn > prevPn) {
                        ResponseEntity<GenericListResponse<ExcelFileDetailVo>> detailList;
                        List<ExcelFileDetailVo> detailListBodyList;
                        try {
                            log.info("{}实责校责 查询清单详细列表 pervPn:{}", findCode, prevPn);
                            detailList = parserClient.excelDetailList(fileId, grade, pn, PS);
                            detailListBodyList = detailList.getBody().getList();
                            prevPn++;
                            if (detailListBodyList.size() == PS && (pn * PS < detailList.getBody().getTotal())) {
                                pn++;
                            }
                        } catch (Exception e) {
                            log.error("{}实责校责 查询清单详细列表失败{}", findCode, appCode);
                            return -1;
                        }

                        resultNum += insertEndorses(detailListBodyList, appCode, fileId, StatusEnum.INSERT.getCode(), usercode);
                        log.info("{}实责校责 成功保存人员清单表记录数:{}", findCode, resultNum);
                    }
                }

            } else {
                // 山东、云南、学责
                ResponseEntity<GenericListResponse<Map<String, String>>> resources;
                List<Map<String, String>> results;
                try {
                    BizResQo bizResQo = new BizResQo();
                    bizResQo.setAppCode(appCode);
                    bizResQo.setBizCodeType("04");
                    bizResQo.setResCode("A101");
                    resources = insClient.getResources(bizResQo);
                    results = resources.getBody().getList();

                    log.info("{}除实责校责外，其余产品查询清单附件结果:{}", findCode, JsonUtil.object2Json(results));
                } catch (Exception e) {
                    log.info("{}查询资源中被保人附件信息失败 投保单号:{}", findCode, appCode);
                    return -1;
                }
                if (CommonUtils.isEmpty(results)) {
                    log.info("{}查询资源中被保人附件信息失败,{}", findCode, appCode);
                    return -1;
                }

                for (Map<String, String> result : results) {
                    String fileId = result.get("resId");
                    if (fileId == null) {
                        continue;
                    }

                    int prevPn = 0;
                    int pn = 1;
                    while (pn > prevPn) {
                        ResponseEntity<GenericListResponse<ExcelFileDetailVo>> detailList;
                        List<ExcelFileDetailVo> detailListBodyList;
                        try {
                            log.info("{}除实责校责外 查询清单详细列表 prevPn:{}", findCode, prevPn);
                            detailList = parserClient.excelDetailList(fileId, null, pn, PS);
                            detailListBodyList = detailList.getBody().getList();
                            prevPn++;
                            if (detailListBodyList.size() == PS && (pn * PS < detailList.getBody().getTotal())) {
                                pn++;
                            }
                        } catch (Exception e) {
                            log.info("{}除实责校责外 查询清单详细列表失败,{}", findCode, appCode);
                            return -1;
                        }

                        resultNum += insertEndorses(detailListBodyList, appCode, fileId, StatusEnum.INSERT.getCode(), usercode);
                        log.info("{}除实责校责外 成功保存人员清单表记录数:{}", findCode, resultNum);
                    }
                }
            }
        }

        return resultNum;
    }

    /**
     * 替换人员清单
     */
    @Transactional
    public boolean updateAppEndorseByFileId(String findCode, String appCode, String fileId, String userCode) throws Exception {

        //删除原有保单清单
        Weekend<EndorseEntity> weekend = new Weekend<>(EndorseEntity.class);
        WeekendCriteria<EndorseEntity, Object> criteria = weekend.weekendCriteria();
        criteria.andEqualTo(INSURANCE_ID, appCode);
        criteria.andEqualTo(IS_DELETED, false);
//        // 过滤状态为 已删除 和 已被替换 的数据
//        criteria.andNotEqualTo(STATUS, StatusEnum.DELETE.getCode());
//        criteria.andNotEqualTo(STATUS, StatusEnum.ORIGIN.getCode());

        EndorseEntity endorseEntity = new EndorseEntity();
        endorseEntity.setIsDeleted(true);
        endorseEntity.setUpdateBy(userCode);
        endorseEntity.setUpdateTime(new Date());
        endorseMapper.updateByExampleSelective(endorseEntity, weekend);
        //插入新清单数据
        int prevPn = 0;
        int pn = 1;
        int resultNum = 0;
        while (pn > prevPn) {
            ResponseEntity<GenericListResponse<ExcelFileDetailVo>> detailList;
            List<ExcelFileDetailVo> detailListBodyList;
            log.info("{}查询清单详细列表 prevPn:{}", findCode, prevPn);
            detailList = parserClient.excelDetailList(fileId, null, pn, PS);
            detailListBodyList = detailList.getBody().getList();
            prevPn++;
            if (detailListBodyList.size() == PS && (pn * PS < detailList.getBody().getTotal())) {
                pn++;
            }
            resultNum += insertEndorses(detailListBodyList, appCode, fileId, StatusEnum.INSERT.getCode(), userCode);

        }
        log.info("{}成功保存人员清单表记录数:{}", findCode, resultNum);
        if (resultNum == 0) {
            log.info("{}查询清单失败或清单无内容", findCode);
            throw new Exception("查询清单失败或清单无内容!");
        }
        return true;
    }


    @Override
    @Transactional
    public int addFiles(String appCode, String usercode, List<String> fileIds) {
        int resultNum = 0;

        for (String fileId : fileIds) {
            int prevPn = 0;
            int pn = 1;
            while (pn > prevPn) {
                ResponseEntity<GenericListResponse<ExcelFileDetailVo>> detailList;
                List<ExcelFileDetailVo> detailListBodyList;
                try {
                    detailList = parserClient.excelDetailList(fileId, null, pn, PS);
                    detailListBodyList = detailList.getBody().getList();
                    prevPn++;
                    if (detailListBodyList.size() == PS && (pn * PS < detailList.getBody().getTotal())) {
                        pn++;
                    }
                } catch (Exception e) {
                    throw new BizException("查询清单详细列表失败");
                }

                resultNum += insertEndorses(detailListBodyList, appCode, fileId, StatusEnum.INSERT.getCode(), usercode);
            }
        }

        return resultNum;
    }

    /**
     * 查最终表信息  查询投保单下最新的在保人员信息
     */
    private List<EndorseEntity> getEndorses(String insuranceId) {
        Weekend<EndorseEntity> weekend = new Weekend<>(EndorseEntity.class);
        WeekendCriteria<EndorseEntity, Object> criteria = weekend.weekendCriteria();
        criteria.andEqualTo(INSURANCE_ID, insuranceId);
        criteria.andEqualTo(IS_DELETED, false);
        // 过滤状态为 已删除 和 已被替换 的数据
        criteria.andNotEqualTo(STATUS, StatusEnum.DELETE.getCode());
        criteria.andNotEqualTo(STATUS, StatusEnum.ORIGIN.getCode());

        return endorseMapper.selectByExample(weekend);
    }

    /**
     * @param insuranceId 投保单号
     * @param grade       年级，没有则置null
     * @param cardNo      证件号
     * @param status      新状态
     * @param usercode    操作用户
     * @return 更新数量
     */
    private Integer updateEndorses(String insuranceId, String grade, String cardNo, Character status, String usercode) {
        Weekend<EndorseEntity> weekend = new Weekend<>(EndorseEntity.class);
        WeekendCriteria<EndorseEntity, Object> criteria = weekend.weekendCriteria();

        criteria.andEqualTo(INSURANCE_ID, insuranceId);
        if (cardNo != null) {
            criteria.andEqualTo(CARD_NO, cardNo);
        }
        if (grade != null && GradeEnum.getCodeByMsg(grade) != 0) {
            criteria.andEqualTo(GRADE, GradeEnum.getCodeByMsg(grade));
        }
        criteria.andEqualTo(IS_DELETED, false);
        // 过滤状态为 已删除 和 已被替换 的数据
        criteria.andNotEqualTo(STATUS, StatusEnum.DELETE.getCode());
        criteria.andNotEqualTo(STATUS, StatusEnum.ORIGIN.getCode());

        List<EndorseEntity> endorseEntities = endorseMapper.selectByExample(weekend);
        if (CommonUtils.isEmpty(endorseEntities)) {
            String errorMsg = "未找到" + grade + "证件号为：" + cardNo + "的人员信息";
            log.error("投保单号为：" + insuranceId + errorMsg);
            throw new BizException(errorMsg);
        }
        int resultNum = 0;
        for (EndorseEntity e : endorseEntities) {
            e.setUpdateBy(usercode);
            e.setStatus(status);    // D or O
            resultNum += endorseMapper.updateByPrimaryKeySelective(e);
        }
        return resultNum;
    }

    /**
     * 投保生效，保存保单数据到最终表
     */
    private Integer insertEndorses(List<ExcelFileDetailVo> detailList, String insuranceId, String fileId, Character status, String usercode) {
        List<EndorseParseVo> endorseParseVos = parseContent(detailList);
        List<EndorseEntity> endorseEntities = new ArrayList<>();

        for (EndorseParseVo parseVo : endorseParseVos) {

            if (StringUtils.isNotBlank(parseVo.getAge()) && parseVo.getAge().length() > 5) {
                //打印异常
                log.info("文件ID为：{}，投保单号为：{}的清单解析中年龄【{}】过长:", fileId, insuranceId, parseVo.getAge());
                parseVo.setAge("");
            }
            EndorseEntity entity = EndorseParseVo.convertEntity(parseVo);
            entity.setInsuranceId(insuranceId);
            entity.setFileId(fileId);
            entity.setStatus(status);
            //由于错误录入，导致证件号字段有错误数据，在这一块判断并只保留35位有效字符
            if (StringUtils.isNotBlank(entity.getCardno())
                    && entity.getCardno().length() > 40) {
                entity.setCardno(entity.getCardno().substring(0, 35));
            }
            entity.setYear("20" + insuranceId.substring(1, 3));
            entity.setCreateBy(usercode);
            entity.setCreateTime(new Date());
            entity.setIsDeleted(false);
            endorseEntities.add(entity);
        }
        if (CollectionUtils.isEmpty(endorseEntities)) {
            return 0;
        }
        return endorseMapper.insertList(endorseEntities);
    }

    /**
     * 读取清单content内容
     */
    private List<EndorseParseVo> parseContent(List<ExcelFileDetailVo> detailEntities) {
        List<EndorseParseVo> parseEntities = new ArrayList<>();

        for (ExcelFileDetailVo detailEntity : detailEntities) {
            EndorseParseVo parseEntity;
            try {
                parseEntity = objectMapper.readValue(detailEntity.getContent(), EndorseParseVo.class);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("ExcelFileDetailVo content json 解析失败");
                throw new ServiceException("服务器内部错误，请联系工作人员协助处理");
            }
            parseEntities.add(parseEntity);
        }
        return parseEntities;
    }

    private Boolean isEqual(String s, int i) {
        return "是".equals(s) && i == 1 || "否".equals(s) && i == 2;
    }

    @Override
    public void endorseEffect() {
        //多个服务同时启动定时任务，在执行体开始时判断是否有其他线程在执行
        //设置缓存失败表示有在执行的任务
        if (!RedisUtil.setSixHoursLock(EDU_ENDORSE_LOCK_KEY)) {
            return;
        }
        String findCode = "EDU_JOB_Endorse_" + System.currentTimeMillis();
        log.info("{}, 扫描人员清单出单入库任务开始执行!", findCode);
        try {
            LocalTime now = LocalTime.now();
            int hour = now.getHour();
            if (hour == 3) {
                log.info("{},批单出单处理，1年内出单 开始", findCode);
                endorseEffect(findCode, getEndorseParam(2, Instant.now().minus(365, ChronoUnit.DAYS)));
            } else {
                log.info("{},批单出单处理，3天内出单 开始", findCode);
                endorseEffect(findCode, getEndorseParam(2, Instant.now().minus(3, ChronoUnit.DAYS)));
            }
        } catch (Exception e) {
            log.error("{}, 执行时异常!", findCode, e);
        } finally {
            //删除缓存
            RedisUtil.delete(EDU_ENDORSE_LOCK_KEY);
        }
        log.info("{}, 扫描人员清单出单入库任务执行结束!", findCode);
    }


    /**
     * 批单出单处理
     *
     * @param entities
     */
    public void endorseEffect(String findCode, List<SaveEndorseParam> entities) {
        log.info("{}, 投保单是否出单定时任务开始。", findCode, JSON.toJSONString(entities));
        for (SaveEndorseParam param : entities) {
            //判断该投保单是否在主流程中处理
            if (!RedisUtil.setLock(10L, EDU_ENDORSE_LOCK_KEY, param.getInsuranceId())) {
                log.info("{}主流程中正在处理 批单:{}", findCode, param.getInsuranceId());
                //正在处理，定时任务将不在处理
                return;
            }
            try {
                //使用代理方式实现单个保单的事务
                ((EndorseService) AopContext.currentProxy()).endorseEffect(findCode, param);
            } catch (Exception e) {
                log.info("{}批单:{} 插入人员清单出错!", findCode, param.getInsuranceId(), e);
            } finally {
                RedisUtil.delete(EDU_ENDORSE_LOCK_KEY, param.getInsuranceId());
            }
        }
        log.info("{}投保单是否出单定时任务结束.共有{}条投保单，被扫描到。", findCode, entities.size());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void endorseEffect(String findCode, SaveEndorseParam param) {
        GenericListResponse<EduPolicyVO> eduPolicyVOGenericListResponse;
        EndorsementsEduQueryVO endorsementsEduQueryVO = new EndorsementsEduQueryVO();
        EduPolicyVO eduPolicyVO;
        try {
            log.info("{}, 批单出单处理, 需要处理批单applyId：{}", findCode, param.getApplyId());
            endorsementsEduQueryVO.setChannelCode("edu");
            endorsementsEduQueryVO.setApplyNo(param.getApplyId());
            endorsementsEduQueryVO.setUserCode(param.getUsercode());
            eduPolicyVOGenericListResponse = polClient.queryEduEndorsementsInfoList(endorsementsEduQueryVO);
            log.info("{}, 批单出单处理, 需要处理批单applyId：{}, 保单服务中心查询结果：", findCode, param.getApplyId(), JSON.toJSONString(eduPolicyVOGenericListResponse));

            //如果正常查询但是没有返回结束集，表示该批单申请号不正确，或已做删单处理
            if (null != eduPolicyVOGenericListResponse && eduPolicyVOGenericListResponse.getCode().equals("0000")
                    && CollectionUtils.isEmpty(eduPolicyVOGenericListResponse.getList())) {
                param.setIsDeleted(true);
                endorseParamMapper.updateByPrimaryKeySelective(param);
                log.info("{}, 批单出单处理, 需要处理批单applyId：{}, 批单申请号不正确，或已做删单处理", findCode, param.getApplyId());
                return;
            }
            //通过批单号及批单申请号查询只会出现一个批单信息
            eduPolicyVO = eduPolicyVOGenericListResponse.getList().get(0);
        } catch (Exception e) {
            log.error("批单人员清单入库，查询批单失败! 入参：{}", JsonUtils.toJson(endorsementsEduQueryVO));
            return;
        }
        if (eduPolicyVO == null) {
            return;
        } else if (!EndorsementsStatusEnum.ENDORS_STATUS_03.getStatus().equals(eduPolicyVO.getStatus())) {
            log.info("{}, 批单出单处理, 需要处理批单applyId：{}, 批单未出单，不处理", findCode, param.getApplyId());
            //如果已经删除 将状态修改为已处理
            if (EndorsementsStatusEnum.ENDORS_STATUS_99.equals(eduPolicyVO.getStatus())) {
                param.setIsDeleted(true);
                endorseParamMapper.updateByPrimaryKeySelective(param);
            }
            return;
        }
        log.info("{}, 批单出单处理, 需要处理批单applyId：{}, 批单出单，插入人员", findCode, param.getApplyId());
        try {
            int i = saveEndorse(param);
            if (i > 0) {
                log.info("{}, 批单出单处理, 需要处理批单applyId：{}, 批单出单，插入人员完成", findCode, param.getApplyId());
                param.setIsDeleted(true);
                endorseParamMapper.updateByPrimaryKeySelective(param);
                return;
            }
        } catch (Exception e) {
            log.info("{}, 批单出单处理, 需要处理批单applyId：{}, 批单出单，插入人员失败", findCode, param.getApplyId(), e);
            throw e;
        }
        param.setIsDeleted(true);
        param.setIsIns(SaveEndorseIsInsEnum.ENDORSE_ERROR.getType());
        endorseParamMapper.updateByPrimaryKeySelective(param);
        log.info("{}, 批单出单处理, 需要处理批单applyId：{}, 批单出单，未成功插入人员，更新isIns为4表示批单出错", findCode, param.getApplyId());
    }

    @Override
    public void insuranceEndorseEffect() {
        //多个服务同时启动定时任务，在执行体开始时判断是否有其他线程在执行
        //设置缓存失败表示有在执行的任务
        if (!RedisUtil.setSixHoursLock(INSURANCE_ENDORSE_LOCK_KEY)) {
            return;
        }
        String findCode = "InsuranceEndorseEffect_" + System.currentTimeMillis();
        log.info("{}, 扫描人员清单出单入库任务开始执行!", findCode);
        try {
            //3天后，1年内出单的批单出单处理
            endorseEffect(findCode, getEndorseParam(2, Instant.now().minus(365, ChronoUnit.DAYS)));
            //3天后，1年内出单的投保单出单处理
            insuranceEffect(findCode, getEndorseParam(1, Instant.now().minus(365, ChronoUnit.DAYS)));
        } catch (Exception e) {
            log.error("{}, 执行时异常!", findCode, e);
        } finally {
            //删除缓存
            RedisUtil.delete(INSURANCE_ENDORSE_LOCK_KEY);
        }
        log.info("{}, 扫描人员清单出单入库任务执行结束!", findCode);
    }

    private List<SaveEndorseParam> getEndorseParam(int i, Instant startTime) {
        Weekend<SaveEndorseParam> weekend = new Weekend<>(SaveEndorseParam.class);
        WeekendCriteria<SaveEndorseParam, Object> criteria = weekend.weekendCriteria();
        criteria.andEqualTo("isIns", i);
        criteria.andEqualTo(IS_DELETED, false);
        criteria.andGreaterThan(CREATE_TIME, startTime);
        return endorseParamMapper.selectByExample(weekend);
    }

    @Override
    public void insuranceEffect() {
        //多个服务同时启动定时任务，在执行体开始时判断是否有其他线程在执行
        //设置缓存失败表示有在执行的任务
        if (!RedisUtil.setSixHoursLock(EDU_INSURANCE_LOCK_KEY)) {
            return;
        }
        String findCode = "EDU_JOB_Insurance" + System.currentTimeMillis();
        log.info("{}, 扫描人员清单出单入库任务开始执行!", findCode);
        try {
            LocalTime now = LocalTime.now();
            int hour = now.getHour();
            if (hour == 3) {
                log.info("{} 1年内出单 开始", findCode);
                insuranceEffect(findCode, getEndorseParam(1, Instant.now().minus(365, ChronoUnit.DAYS)));
            } else {
                log.info("{} 3天内出单 开始", findCode);
                insuranceEffect(findCode, getEndorseParam(1, Instant.now().minus(3, ChronoUnit.DAYS)));
            }
        } catch (Exception e) {
            log.error("{}, 执行时异常!", findCode, e);
        } finally {
            //删除缓存
            RedisUtil.delete(EDU_INSURANCE_LOCK_KEY);
        }
        log.info("{}, 扫描人员清单出单入库任务执行结束!", findCode);
    }

    /**
     * 投保单出单处理
     *
     * @param entities
     */
    public void insuranceEffect(String findCode, List<SaveEndorseParam> entities) {
        log.info("{}投保单是否出单定时任务开始.", findCode);
        for (SaveEndorseParam param : entities) {
            //判断该投保单是否在主流程中处理
            if (!RedisUtil.setLock(10L, EDU_INSURANCE_LOCK_KEY, param.getInsuranceId())) {
                log.info("{}主流程中正在处理 投保单:{}", findCode, param.getInsuranceId());
                //正在处理，定时任务将不在处理
                return;
            }
            try {
                //使用代理方式实现单个保单的事务
                ((EndorseService) AopContext.currentProxy()).insuranceEffect(findCode, param);
            } catch (Exception e) {
                log.info("{}投保单:{} 插入人员清单出错!", findCode, param.getInsuranceId(), e);
            } finally {
                RedisUtil.delete(EDU_INSURANCE_LOCK_KEY, param.getInsuranceId());
            }
        }
        //todo 添加协议
        log.info("{}投保单是否出单定时任务结束.共有{}条投保单，被扫描到。", findCode, entities.size());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insuranceEffect(String findCode, SaveEndorseParam param) {
        log.info("{}投保单是否出单定时任务执行投保单:{}", findCode, param.getInsuranceId());
        ResponseEntity<GenericDataResponse<AppDetailProVo>> appDetailPro;
        AppDetailProVo detailVos;
        AppDetailProParam p = new AppDetailProParam();
        p.setAppCode(param.getInsuranceId());
        p.setChannelCode("edu");    // 渠道编号
        p.setUserCode(param.getUsercode());
        try {
            appDetailPro = insClient.searchAppDetailPro(p);
            log.info("{}查询投保单详情 投保单号:{}，返回结果:{}", findCode, param.getInsuranceId(), JsonUtil.object2Json(appDetailPro));
            detailVos = appDetailPro.getBody().getData();
        } catch (Exception e) {
            log.info("{}查询投保单信息失败 投保单号:{}", findCode, param.getInsuranceId());
            param.setIsDeleted(true);
            endorseParamMapper.updateByPrimaryKeySelective(param);
            log.info("{}修改isDeleted状态为true 投保单号:{}", findCode, param.getInsuranceId());
            return;
        }
        // 待出单（08）出单失败(09) 已出单（10） 已承保(21山东险种)
        if (detailVos == null
                || AppStatusEnum.AS_99.getCode().equals(detailVos.getApplicationStatus())
                || AppStatusEnum.AS_11.getCode().equals(detailVos.getApplicationStatus())
                || AppStatusEnum.AS_12.getCode().equals(detailVos.getApplicationStatus())) {
            log.info("{}查询投保单号:{},投保单详情为空或投保单状态已失效,修改isDeleted状态为true ", findCode, param.getInsuranceId());
            param.setIsDeleted(true);
            endorseParamMapper.updateByPrimaryKeySelective(param);
            return;
        } else if (!AppStatusEnum.AS_10.getCode().equals(detailVos.getApplicationStatus())
                && !AppStatusEnum.AS_21.getCode().equals(detailVos.getApplicationStatus())) {
            log.info("{}该投保单状态不是10或21 投保单号:{}，投保单状态:{}", findCode, param.getInsuranceId(), detailVos.getApplicationStatus());
            return;
        }

        PolicyTypeEnum policyTypeEnum = PolicyTypeEnum.getByProductCode(detailVos.getProductCode());
        boolean policyTypeFlag;
        if (policyTypeEnum == null) {
            log.error("{}不能识别的险种信息:{},投保单号:{}", findCode, detailVos.getProductCode(), param.getInsuranceId());
            return;
        } else policyTypeFlag = policyTypeEnum == PolicyTypeEnum.TRAINEE_ENT
                || policyTypeEnum == PolicyTypeEnum.TRAINEE_SCH
                || policyTypeEnum == PolicyTypeEnum.STUDENT_SCH;

        int i = 0;
        if (policyTypeEnum != PolicyTypeEnum.YN_SP) {
            i = saveInsurance(Collections.singletonList(param.getInsuranceId()), param.getUsercode(), policyTypeFlag, false);
        }
        if (i > 0 || policyTypeEnum == PolicyTypeEnum.YN_SP) {
            param.setIsDeleted(true);
            endorseParamMapper.updateByPrimaryKeySelective(param);
        }
    }

    @Override
    public int saveEndorseParam(SaveEndorseParam param) {

        int num = 0;

        param.setIsIns(2);

        // 协议批加 fileId为多个，以逗号分隔
        String[] files = param.getFileId().split(",");

        for (String file : files) {
            param.setFileId(file);
            num += endorseParamMapper.insertSelective(param);
        }

        return num;
    }

    @Override
    @Transactional
    public int saveInsuranceParam(List<String> appCodes, String usercode, String findCode) {

        log.info("{} service 开始", findCode);

        int num = 0;
        for (String appCode : appCodes) {
            SaveEndorseParam param = new SaveEndorseParam();
            param.setInsuranceId(appCode);
            param.setUsercode(usercode);
            param.setIsIns(1);
            num += endorseParamMapper.insertSelective(param);

            log.info("{} t_save_endorse_param 保存完成 SaveEndorseParam->{},num->{}", findCode, JsonUtil.object2Json(param), num);
        }
        log.info("{} service 完成 num->{}", findCode, num);
        return num;
    }

    @Override
    public int effectiveInsParam(String appCode) {
        Weekend<SaveEndorseParam> weekend = new Weekend<>(SaveEndorseParam.class);
        WeekendCriteria<SaveEndorseParam, Object> criteria = weekend.weekendCriteria();
        criteria.andEqualTo("insuranceId", appCode);
        criteria.andEqualTo("isIns", 1);
        criteria.andEqualTo(IS_DELETED, false);
        List<SaveEndorseParam> endorseParams = endorseParamMapper.selectByExample(weekend);
        if (CommonUtils.isEmpty(endorseParams)) {
            return 0;
        } else {
            int num = 0;
            for (SaveEndorseParam e : endorseParams) {
                e.setCreateTime(new Date());
                num += endorseParamMapper.updateByPrimaryKeySelective(e);
            }
            return num;
        }
    }

    @Override
    public int effectiveEndParam(String endorOrderCode) {
        Weekend<SaveEndorseParam> weekend = new Weekend<>(SaveEndorseParam.class);
        WeekendCriteria<SaveEndorseParam, Object> criteria = weekend.weekendCriteria();
        criteria.andEqualTo("applyId", endorOrderCode);
        criteria.andEqualTo("isIns", 2);
        criteria.andEqualTo(IS_DELETED, false);
        List<SaveEndorseParam> endorseParams = endorseParamMapper.selectByExample(weekend);
        if (CommonUtils.isEmpty(endorseParams)) {
            return 0;
        } else {
            int num = 0;
            for (SaveEndorseParam e : endorseParams) {
                e.setCreateTime(new Date());
                num += endorseParamMapper.updateByPrimaryKeySelective(e);
            }
            return num;
        }
    }

    @Override
    public Map replaceDetail(String replaceFileId, String originFileId, Integer policyType) {
        List<EndorseParseVo> replaceParseVos;
        List<EndorseParseVo> originParseVos;

        try {
            ResponseEntity<GenericListResponse<ExcelFileDetailVo>> excelDetailVo =
                    parserClient.excelDetailList(replaceFileId, null, 1, Integer.MAX_VALUE);
            List<ExcelFileDetailVo> replaceList = excelDetailVo.getBody().getList();
            replaceParseVos = parseContent(replaceList);
        } catch (Exception e) {
            throw new BizException("调用清单解析-清单详细列表接口 查询替换清单 失败");
        }
        try {
            ResponseEntity<GenericListResponse<ExcelFileDetailVo>> excelDetailVo =
                    parserClient.excelDetailList(originFileId, null, 1, Integer.MAX_VALUE);
            List<ExcelFileDetailVo> originList = excelDetailVo.getBody().getList();
            originParseVos = parseContent(originList);
        } catch (Exception e) {
            throw new BizException("调用清单解析-清单详细列表接口 查询被替换清单 失败");
        }

        // 当直接查询清单服务查询不到时，看文件服务器是否能查到文件，能查到则上传清单解析获取清单内容，主要为兼容旧系统的迁移数据
        if (CommonUtils.isEmpty(replaceParseVos)) {
            if (policyType == PolicyTypeEnum.SD_XF.getCode()) {
                policyType = PolicyTypeEnum.SD_XF_OLD.getCode();
            }

            Response response = fileService.downloadFileByFileUrl(replaceFileId);
            try (InputStream stream = response.body().asInputStream()) {
                LinkedList<String> headers = (LinkedList<String>) response.headers().get("content-disposition");
                String header = headers.get(0);
                String filename = header.substring(header.indexOf("=") + 1);
                byte[] bytes = CommonUtils.readStream(stream);
                InputStream inputStream = new ByteArrayInputStream(bytes);
                MultipartFile file = new MockMultipartFile(filename, filename, ContentType.APPLICATION_OCTET_STREAM.toString(), inputStream);

                log.info("开始调用清单解析-上传清单接口");
                ResponseEntity<GenericDataResponse<ParserResultVo>> replaceVo =
                        parserClient.analysisExcelByFile(file, PolicyTypeEnum.getByCode(policyType).getModelCode(), DETAIL, SaveType.ONLY_SAVE_DATA, CHANNEL_CODE);
                replaceParseVos = replaceVo.getBody().getData().getDatas();
            } catch (Exception e) {
                throw new BizException("替换清单内容为空");
            }
        }
        if (CommonUtils.isEmpty(originParseVos)) {
            if (policyType == PolicyTypeEnum.SD_XF.getCode()) {
                policyType = PolicyTypeEnum.SD_XF_OLD.getCode();
            }

            Response response = fileService.downloadFileByFileUrl(originFileId);
            try (InputStream stream = response.body().asInputStream()) {
                LinkedList<String> headers = (LinkedList<String>) response.headers().get("content-disposition");
                String header = headers.get(0);
                String filename = header.substring(header.indexOf("=") + 1);
                byte[] bytes = CommonUtils.readStream(stream);
                InputStream inputStream = new ByteArrayInputStream(bytes);
                MultipartFile file = new MockMultipartFile(filename, filename, ContentType.APPLICATION_OCTET_STREAM.toString(), inputStream);

                log.info("开始调用清单解析-上传清单接口");
                ResponseEntity<GenericDataResponse<ParserResultVo>> replaceVo =
                        parserClient.analysisExcelByFile(file, PolicyTypeEnum.getByCode(policyType).getModelCode(), DETAIL, SaveType.ONLY_SAVE_DATA, CHANNEL_CODE);
                originParseVos = replaceVo.getBody().getData().getDatas();
            } catch (Exception e) {
                throw new BizException("被替换清单内容为空");
            }
        }

        if (CommonUtils.isEmpty(replaceParseVos) || CommonUtils.isEmpty(originParseVos)) {
            throw new BizException("未查询到清单内容");
        }

        Map<String, List<Map<String, String>>> replaceMap = new LinkedHashMap<>();
        Map<String, List<Map<String, String>>> originMap = new LinkedHashMap<>();

        replaceParseVos.forEach(e -> {
            GradeEnum gradeEnum = GradeEnum.getByMsg(e.getGrade());
            String key;
            if (gradeEnum == null) {
                key = "grade";
            } else {
                key = "grade" + gradeEnum.getCode();
            }
            Map<String, String> detail = new HashMap<>();
            detail.put("name", e.getName());
            detail.put("sex", e.getSex());
            detail.put("certificateType", e.getCertificateType());
            detail.put("cardNo", e.getCardNo());
            if (replaceMap.containsKey(key)) {
                replaceMap.get(key).add(detail);
            } else {
                List<Map<String, String>> detailList = new ArrayList<>();
                detailList.add(detail);
                replaceMap.put(key, detailList);
            }
        });
        originParseVos.forEach(e -> {
            GradeEnum gradeEnum = GradeEnum.getByMsg(e.getGrade());
            String key;
            if (gradeEnum == null) {
                key = "grade";
            } else {
                key = "grade" + gradeEnum.getCode();
            }
            Map<String, String> detail = new HashMap<>();
            detail.put("name", e.getName());
            detail.put("sex", e.getSex());
            detail.put("certificateType", e.getCertificateType());
            detail.put("cardNo", e.getCardNo());
            if (originMap.containsKey(key)) {
                originMap.get(key).add(detail);
            } else {
                List<Map<String, String>> detailList = new ArrayList<>();
                detailList.add(detail);
                originMap.put(key, detailList);
            }
        });

        Map result = new HashMap();
        result.put("replace", replaceMap);
        result.put("origin", originMap);

        return result;
    }


    /**
     * 通过不同的批单规则获取批单不同险种组合及其年级对应的优惠和非优惠人数
     *
     * @param paramList
     * @return
     * @throws Exception
     */
    @Override
    public List<CheckNumVO> checkPayPersonNum(List<CheckNumVO> paramList) throws Exception {
        List<CheckNumVO> copyList = this.deepCopyList(paramList);
        if (null == paramList || paramList.isEmpty()) {
            log.error("获取批单优惠及其不优惠人数接口=>>>入参={}", paramList);
            throw new Exception("请求数据丢失，请刷新页面重试");
        }
        String param = JsonUtil.object2Json(paramList);

        log.info("获取批单优惠及其不优惠人数接口=>>>入参={}", param);
        String productCode = paramList.get(0).getProCode();
        String type = paramList.get(0).getType();
        Integer currentNum = paramList.get(0).getCurrentNum();
        /**
         * 云南教职工 校园食品安全责任保险 不用做处理
         */
        if (YNJZG_PRODUCT_CODE.equals(productCode) || PolicyTypeEnum.SP_XZ.getProductCode().equals(productCode)) {
            // 因为不需要处理，所以把原参数返回。
            return paramList;
        }
        /**
         * 获取可以优惠人数
         */
        Integer subNum = this.getNumByPolServer(paramList);


        if (XZ_PRODUCT_CODE.equals(productCode)) {
            // 获取优惠人数
            List<CheckNumVO> list = this.checkQGSZNum(checkZxNum(paramList), subNum);
            //获取未优惠人数
            getNoDiscountForXZ(list, copyList);
            return copyList;
        } else if (SZ_PRODUCT_CODE.equals(productCode)) {
            // 实责：计算各险种人数
            //优惠人数
            List<CheckNumVO> list = this.checkQGSZNum(szCheckZxNum(paramList), subNum);
            //获取未优惠人数
            getNoDiscountNumForSZ(list, copyList);
            return copyList;
        }
        CheckNumVO checkNumVO = paramList.get(0);
        if (subNum > 0) {
            int i = currentNum - subNum;
            if (i >= 0) {
                checkNumVO.setFfNum(currentNum - subNum);
                checkNumVO.setMfNum(subNum);
            } else {
                checkNumVO.setFfNum(0);
                checkNumVO.setMfNum(currentNum);
            }
        } else {
            checkNumVO.setMfNum(0);
            checkNumVO.setFfNum(currentNum);
        }
        return paramList;
    }

    /**
     * 实责校责根据批改规则（按照年级和险种组合优先级分配人数）对需要优惠的人数进行处理
     *
     * @param paramList
     * @param subNum
     * @return
     * @throws Exception
     */
    private List<CheckNumVO> checkQGSZNum(List<CheckNumVO> paramList, Integer subNum) {
        // 和前端沟通，需要返回  按照险种人数的汇总。举例：双附加险 1人，只买附加险A的1人。返回 双附加险1人  附加险A2人。
        String type = PdTypeEnum.getValueByPdType(paramList.get(0).getType());
        if (NO.equals(PdTypeEnum.getValueByPdType(type))) {
            log.info("获取批单优惠及其不优惠人数接口=>>>批改类型:type={},不参与优惠直接返回参数", type);
            return paramList;
        }
        String productCode = paramList.get(0).getProCode();
        return calculateTwoNumNew(paramList, subNum, StringUtils.equals("add", type) ? (byte) 7 : (byte) 0, 0, new HashMap<>(), StringUtils.equals("add", type) ? "X" : "Y", type, productCode);
    }

    /**
     * 通过查询历史保单数据和当前保单数据计算需要优惠人数
     *
     * @param paramList
     * @return
     * @throws Exception
     */
    private Integer getNumByPolServer(List<CheckNumVO> paramList) throws Exception {
        log.info("计算本次可优惠人数---------开始--------");
        Integer originalNum = paramList.get(0).getOriginalNum();
        String policyNo = paramList.get(0).getPolicyNo();
        String type = PdTypeEnum.getValueByPdType(paramList.get(0).getType());
        String proCode = paramList.get(0).getProCode();
        Integer totalNum = 0;
        CheckNumVO checkNumVO = paramList.get(0);
        // 首先调用批单中心接口  查询批单记录信息
        ResponseEntity<GenericListResponse<EduQueryEndorsementTypeAndCountInfo>> genericListResponseResponseEntity = polClient.queryEduEndorsemenstInfo(policyNo);

        GenericListResponse<EduQueryEndorsementTypeAndCountInfo> body = genericListResponseResponseEntity.getBody();
        String code = body.getCode();
        String valueByPdType = "";
        if ("0000".equals(code)) {
            // 如果本次是批加。那么从记录中只取批加的记录。
            List<EduQueryEndorsementTypeAndCountInfo> list = body.getList();
            if (list != null && list.size() > 0) {
                for (EduQueryEndorsementTypeAndCountInfo vo : list) {
                    //删除单不计算
                    if (EndorsementsStatusEnum.ENDORS_STATUS_99.getStatus().equals(vo.getStatus())) {
                        continue;
                    }
                    // 先判断类型  然后这一个对象中 所有的数据都是这一个类型  ok   todo
                    String endorsementsType = vo.getEndorsementsType();
                    valueByPdType = PdTypeEnum.getValueByPdType(endorsementsType);
                    if (NO.equals(valueByPdType)) {
                        continue;
                    }
                    if (type.equals(valueByPdType)) {
                        if (Arrays.asList("01", "04", "11", "12", "13", "14").contains(endorsementsType)) {
                            continue;
                        }
                        List<EduInsuredInfo> insuredList = vo.getInsuredList();
                        if (insuredList != null && insuredList.size() > 0) {
                            for (EduInsuredInfo eduInsuredInfo : insuredList) {
                                List<EduInsuredObject> insuranceObjects = eduInsuredInfo.getInsuranceObjects();
                                if (insuranceObjects != null && insuranceObjects.size() > 0) {
                                    for (EduInsuredObject eduInsuredObject : insuranceObjects) {
                                        List<ContextExtend> extendList = eduInsuredObject.getExtendList();
                                        if (extendList != null && extendList.size() > 0) {
                                            for (ContextExtend contextExtend : extendList) {
                                                String key = contextExtend.getKey();
                                                //优惠5%
                                                String productCodes = PolicyTypeEnum.getProductCodeByPreferentialWay(FIVE_PERCENT);
                                                if (StringUtils.isNotBlank(productCodes) && productCodes.contains(proCode)) {// 山东校责
                                                    if ("S_O_100004".equals(key)) {
                                                        String value = contextExtend.getValue();
                                                        if (StringUtils.isEmpty(value)) {
                                                            value = "0";
                                                        }
                                                        Integer num = Integer.valueOf(value);
                                                        totalNum += num;
                                                    }
                                                }
                                                // 云南-校方综合保障,校方责任,食品安全责任  云南教职工
                                                if (YN_PRODUCT_CODES.contains(proCode)) {
                                                    if ("S_O_100022".equals(key)) {
                                                        String value = contextExtend.getValue();
                                                        if (StringUtils.isEmpty(value)) {
                                                            value = "0";
                                                        }
                                                        Integer num = Integer.valueOf(value);
                                                        totalNum += num;
                                                    }
                                                }
                                                if (QG_PRODUCT_CODES.contains(proCode)) {
                                                    if ("S_O_100004".equals(key)) {
                                                        String value = contextExtend.getValue();
                                                        if (StringUtils.isEmpty(value)) {
                                                            value = "0";
                                                        }
                                                        Integer num = Integer.valueOf(value);
                                                        if ("03".equals(endorsementsType)) {
                                                            totalNum += num;
                                                        } else if ("02".equals(endorsementsType)) {
                                                            totalNum += num;
                                                        } else if ("05".equals(endorsementsType)) {
                                                            log.info("有过协议批加的记录，原保单人数增加");
                                                            originalNum += num;
                                                        } else if ("06".equals(endorsementsType)) {
                                                            log.info("有过协议批减的记录，原保单人数减少");
                                                            originalNum -= num;
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } else {
            log.error("保单中心返回错误信息：msg={}", body.getMsg());
            throw new Exception("调用批单中心查询批单记录失败");
        }
        log.info("保单中心查询批改已参与人数：totalNum={}", totalNum);
        log.info("本次参与批改总人数（受协议批改影响）：originalNum={}", originalNum);
        // 第二步：原始人数乘以优惠率  计算应该优惠的人数 num
        Double rate = Double.valueOf(benefitRate);
        log.info("优惠率：rate={}", rate);
        // 四舍五入
        Long round = Math.round(originalNum * rate);
        int num = round.intValue();
        log.info("本次批改可优惠人数：num={}", num);
        log.info("计算本次可优惠人数---------结束--------");
        return num - totalNum;
    }

    /**
     * 实责校责根据批改规则（按照年级和险种组合优先级分配人数）对需要优惠的人数进行处理
     *
     * @param resultList
     * @param subNum
     * @param grade
     * @param highGradeNum
     * @param map
     * @return
     */
    private List<CheckNumVO> calculateTwoNumNew(List<CheckNumVO> resultList, int subNum, byte grade, Integer highGradeNum, Map<String, Integer> map, String type, String endorsementsType, String productCode) {
        //无优惠人数
        if (subNum <= 0) {
            this.GetNumOfDiscountByRiskType(resultList, grade, 0, type, productCode);
            return resultList;
        }
        // FIXME 以实责批减为例（批加规则相反）： 获取到传参中的数据 先扣除有两个附加险的，再扣除实习无过失，附加被保险人，从高年级先扣。
        if (highGradeNum != null && highGradeNum > 0) {
            int i = subNum - highGradeNum;
            if (i > 0) {
                //返回值赋值
                GetNumOfDiscountByRiskType(resultList, grade, highGradeNum, type, productCode);
                //剩余优惠人数
                subNum = subNum - highGradeNum;
                //下一个年级
                if (StringUtils.equals(endorsementsType, "add")) {
                    grade -= 1;
                } else {
                    grade += 1;
                }
                String gradeCode = GradeEnum.getEnCodeByCode(grade);
                highGradeNum = map.get(gradeCode);
                if (StringUtils.equals(endorsementsType, "add")) {
                    if (grade > 0) {
                        calculateTwoNumNew(resultList, subNum, grade, highGradeNum, map, type, endorsementsType, productCode);
                    } else {
                        return resultList;
                    }
                } else {
                    if (grade < 8) {
                        calculateTwoNumNew(resultList, subNum, grade, highGradeNum, map, type, endorsementsType, productCode);
                    } else {
                        return resultList;
                    }
                }
            } else {
                GetNumOfDiscountByRiskType(resultList, grade, subNum, type, productCode);
            }
        } else {
            // 去找下一年级
            if (StringUtils.equals(endorsementsType, "add")) {
                grade -= 1;
                String gradeCode = GradeEnum.getEnCodeByCode(grade);
                // 最高年级的两个附加险的人数
                highGradeNum = map.get(gradeCode);
                if (highGradeNum == null) {
                    highGradeNum = 0;
                }
                if (grade > 0) {
                    return calculateTwoNumNew(resultList, subNum, grade, highGradeNum, map, type, endorsementsType, productCode);
                }
                //下一个险种组合
                if (StringUtils.equals(endorsementsType, "add")) {
                    if (StringUtils.equals(type, "D") || StringUtils.equals(type, "J")) {
                        return resultList;
                    }
                } else {
                    if (StringUtils.equals(type, "A") || StringUtils.equals(type, "E")) {
                        return resultList;
                    }
                }
                type = getRiskTypeByEndType(type, endorsementsType, productCode);
                // 说明 扣完双附加险 的人数后 ，优惠名额还有剩余，需要继续递归循环，去循环 实习无过失的人数，也是从高年级向低年级依次扣
                // 首先组装一个 所有年级人数的map
                map = getNumMapByRiskType(resultList, type, productCode);
                if (StringUtils.equals(endorsementsType, "add")) {
                    grade = 7;
                } else {
                    grade = 1;
                }
                gradeCode = GradeEnum.getEnCodeByCode(grade);
                // 最高年级的只买 实习无过失 的人数
                highGradeNum = map.get(gradeCode);
                if (highGradeNum == null) {
                    highGradeNum = 0;
                }
                return calculateTwoNumNew(resultList, subNum, grade, highGradeNum, map, type, endorsementsType, productCode);
            } else {
                grade += 1;
                String gradeCode = GradeEnum.getEnCodeByCode(grade);
                // 最高年级的两个附加险的人数
                highGradeNum = map.get(gradeCode);
                if (highGradeNum == null) {
                    highGradeNum = 0;
                }
                if (grade < 8) {
                    return calculateTwoNumNew(resultList, subNum, grade, highGradeNum, map, type, endorsementsType, productCode);
                }
                if (subNum > 0) {
                    if (StringUtils.equals(endorsementsType, "add")) {
                        if (StringUtils.equals(type, "D") || StringUtils.equals(type, "J")) {
                            return resultList;
                        }
                    } else {
                        if (StringUtils.equals(type, "A") || StringUtils.equals(type, "E")) {
                            return resultList;
                        }
                    }
                    //下一个险种组合
                    type = getRiskTypeByEndType(type, endorsementsType, productCode);
                    // 说明 扣完双附加险 的人数后 ，优惠名额还有剩余，需要继续递归循环，去循环 实习无过失的人数，也是从高年级向低年级依次扣
                    // 首先组装一个 所有年级人数的map
                    map = getNumMapByRiskType(resultList, type, productCode);
                    if (StringUtils.equals(endorsementsType, "add")) {
                        grade = 7;
                    } else {
                        grade = 0;
                    }
                    gradeCode = GradeEnum.getEnCodeByCode(grade);
                    // 最高年级的只买 实习无过失 的人数
                    highGradeNum = map.get(gradeCode);
                    if (highGradeNum == null) {
                        highGradeNum = 0;
                    }
                    return calculateTwoNumNew(resultList, subNum, grade, highGradeNum, map, type, endorsementsType, productCode);
                }
            }

        }
        return resultList;
    }

    /**
     * 通过已知的优惠人数和总人数计算 [付费人数 校责]
     *
     * @param list
     */
    private void getNoDiscountForXZ(List<CheckNumVO> list, List<CheckNumVO> copyList) {
        log.info("通过优惠人数计算各个年级的不优惠人数--------开始-------");
        for (CheckNumVO numVO : list) {
            log.info("年级：{}", numVO.getGrade());
            //除了只买主险剩余得优惠总人数
            Integer mNum = numVO.getXz1MFNum() + numVO.getXz2MFNum() + numVO.getXz3MFNum() + numVO.getXzMfNum() + numVO.getXzcrbMFNum();
            log.info("除了只买主险剩余得优惠总人数：{}", mNum);
            /**
             *
             * A-主险 B-校方 C-校外 D-传染病
             *
             */

            //
            //abcd
            Integer abcd = numVO.getXz1MFNum();
            numVO.setXz1MFNum(numVO.getXz1MFNum());
            log.info("abcd[主险+校方+校外+传染病]优惠人数：{}", numVO.getXz1MFNum());

            Integer abc = numVO.getXz2MFNum();
            log.info("abc[主险+校方+校外]优惠人数：{}", abc);
            Integer abd = numVO.getXz3MFNum();
            log.info("abd[主险+校方+传染病]优惠人数：{}", abd);
            Integer ab = numVO.getXzMfNum();
            log.info("ab[主险+校方]优惠人数：{}", ab);
            Integer ad = numVO.getXzcrbMFNum();
            log.info("ad[主险+传染病]优惠人数：{}", ad);

            //校方免费人数 abc         abcd+abc
            numVO.setXz2MFNum(abcd + abc);
            //校方免费人数 abd        abcd+abd
            numVO.setXz3MFNum(abcd + abd);
            //校方免费人数 ab         abcd+abc+abd+ab
            numVO.setXzMfNum(abcd + abc + abd + ab);
            //校方免费人数 ad       abcd+abd+ad
            numVO.setXzcrbMFNum(abcd + abd + ad);

            for (CheckNumVO checkNumVO : copyList) {
                if (StringUtils.equals(numVO.getGrade(), checkNumVO.getGrade())) {
                    //abcd
                    checkNumVO.setXz1MFNum(numVO.getXz1MFNum());
                    //校方免费人数 abc
                    checkNumVO.setXz2MFNum(numVO.getXz2MFNum());
                    //校方免费人数 abd
                    checkNumVO.setXz3MFNum(numVO.getXz3MFNum());
                    //校方免费人数 ab
                    checkNumVO.setXzMfNum(numVO.getXzMfNum());
                    //校方免费人数 ad
                    checkNumVO.setXzcrbMFNum(numVO.getXzcrbMFNum());
                    //主险
                    //checkNumVO.setXzNum2(numVO.getXzNum2());
                    checkNumVO.setXzNum3(numVO.getXzNum3() + mNum);

                    //计算付费
                    //abcd
                    if (numVO.getXz1Num().compareTo(0) > 0) {
                        checkNumVO.setXz1FFNum(numVO.getXz1Num() - abcd);
                        log.info("abcd[主险+校方+校外+传染病] 总人数：{}，免费人数：{}，付费人数：{}", numVO.getXz1Num(), abcd, checkNumVO.getXz1FFNum());
                    }
                    //abc
                    if (numVO.getXz2Num().compareTo(0) > 0) {

                        checkNumVO.setXz2FFNum(numVO.getXz2Num() - abc);
                        log.info("abcd[主险+校方+校外] 总人数：{}，免费人数：{}，付费人数：{}", numVO.getXz2Num(), checkNumVO.getXz2MFNum(), abc);
                    }
                    //abd
                    if (numVO.getXz3Num().compareTo(0) > 0) {
                        checkNumVO.setXz3FFNum(numVO.getXz3Num() - abd);
                        log.info("abcd[主险+校方+传染病] 总人数：{}，免费人数：{}，付费人数：{}", numVO.getXz3Num(), checkNumVO.getXz3MFNum(), abd);
                    }
                    //ab
                    if (numVO.getXzNum1().compareTo(0) > 0) {
                        checkNumVO.setXzFfNum(numVO.getXzNum1() - ab);
                        log.info("abcd[主险+校方] 总人数：{}，免费人数：{}，付费人数：{}", numVO.getXzNum1(), checkNumVO.getXzMfNum(), ab);
                    }
                    //ad
                    if (numVO.getXzcrbNum().compareTo(0) > 0) {
                        checkNumVO.setXzcrbFFNum(numVO.getXzcrbNum() - ad);
                        log.info("abcd[主险+传染病] 总人数：{}，免费人数：{}，付费人数：{}", numVO.getXzcrbNum(), checkNumVO.getXzcrbMFNum(), ad);
                    }
                    //主险付费人数
                    checkNumVO.setXzNum4((checkNumVO.getGradeNum() - checkNumVO.getXzNum3()) < 0 ? 0 : (checkNumVO.getGradeNum() - checkNumVO.getXzNum3()));
                    log.info("a[主险] 总人数：{}，免费人数：{}，付费人数：{}", checkNumVO.getGradeNum(), checkNumVO.getXzNum3(), checkNumVO.getXzNum4());
                    /**
                     * @ApiModelProperty("校责-[校方]免费人数")
                     *     private Integer xfMFNum = 0;
                     *     @ApiModelProperty("校责-[校方]付费人数")
                     *     private Integer xfFFNum = 0;
                     *     @ApiModelProperty("校责-[校外]免费人数")
                     *     private Integer xwMFNum = 0;
                     *     @ApiModelProperty("校责-[校外]付费人数")
                     *     private Integer xwFFNum = 0;
                     *
                     *     @ApiModelProperty("校责-[传染病]免费人数")
                     *     private Integer crbMFNum = 0;
                     *     @ApiModelProperty("校责-[传染病]付费人数")
                     *     private Integer crbFFNum = 0;
                     *
                     *     @ApiModelProperty("校责-[主险]免费人数")
                     *     private Integer zxMFNum = 0;
                     *     @ApiModelProperty("校责-[主险]付费人数")
                     *     private Integer zxFFNum = 0;
                     */

                    /**
                     * A-主险 B-校方 C-校外 D-传染病
                     */
                    log.info("最终统计结果如下：");
                    //abcd+abc+abd+ab
                    checkNumVO.setXfMFNum(abcd + abc + abd + ab);
                    checkNumVO.setXfFFNum(checkNumVO.getXz1FFNum() + checkNumVO.getXz2FFNum() + checkNumVO.getXz3FFNum() + checkNumVO.getXzFfNum());
                    log.info("校方付费人数：{}，免费人数：{}", checkNumVO.getXfFFNum(), checkNumVO.getXfMFNum());
                    //abcd+abc
                    checkNumVO.setXwMFNum(abcd + abc);
                    checkNumVO.setXwFFNum(checkNumVO.getXz1FFNum() + checkNumVO.getXz2FFNum());
                    log.info("校外付费人数：{}，免费人数：{}", checkNumVO.getXwFFNum(), checkNumVO.getXwMFNum());
                    //abcd+abd+ad
                    checkNumVO.setCrbMFNum(abcd + abd + ad);
                    checkNumVO.setCrbFFNum(checkNumVO.getXz1FFNum() + checkNumVO.getXz3FFNum() + checkNumVO.getXzcrbFFNum());
                    log.info("传染病付费人数：{}，免费人数：{}", checkNumVO.getCrbFFNum(), checkNumVO.getCrbMFNum());
                    //abcd+abc+abd+ab+ad+a
                    checkNumVO.setZxMFNum(checkNumVO.getXzNum3());
                    checkNumVO.setZxFFNum(checkNumVO.getXzNum4());
                    log.info("主险付费人数：{}，免费人数：{}", checkNumVO.getZxFFNum(), checkNumVO.getZxMFNum());
                }
            }
        }
        log.info("通过优惠人数计算各个年级的不优惠人数-----------------结束---------------");
    }

    /**
     * 通过已知的优惠人数和总人数计算为 [优惠人数 实责]
     *
     * @param list
     */
    private void getNoDiscountNumForSZ(List<CheckNumVO> list, List<CheckNumVO> copyList) {
        for (CheckNumVO numVO : list) {
            Integer mNum = numVO.getSxAInsMfNum() + numVO.getInsSxMfNum() + numVO.getInsMfNum();
            //附加实习免费人数
            numVO.setInsSxMfNum(numVO.getSxAInsMfNum() + numVO.getInsSxMfNum());
            //附加被保险免费人数
            numVO.setInsMfNum(numVO.getSxAInsMfNum() + numVO.getInsMfNum());
            for (CheckNumVO checkNumVO : copyList) {
                if (StringUtils.equals(numVO.getGrade(), checkNumVO.getGrade())) {
                    //附加实习和附加被保险同时投保人数 免费人数
                    checkNumVO.setSxAInsMfNum(numVO.getSxAInsMfNum());
                    //附加实习免费人数
                    checkNumVO.setInsSxMfNum(numVO.getInsSxMfNum());
                    //附加被保险免费人数
                    checkNumVO.setInsMfNum(numVO.getInsMfNum());
                    //主险
                    checkNumVO.setInsNum5(numVO.getInsNum5() + mNum);
                    checkNumVO.setInsNum4(numVO.getInsNum4());
                    //附加实习和附加被保险同时投保人数 付费人数
                    if (checkNumVO.getInsNum3().compareTo(0) > 0) {
                        checkNumVO.setSxAInsFfNum(checkNumVO.getInsNum3() - checkNumVO.getSxAInsMfNum());
                    }
                    //附加实习付费人数
                    if (checkNumVO.getInsNum1().compareTo(0) > 0) {
                        checkNumVO.setInsSxFfNum(checkNumVO.getInsNum1() - checkNumVO.getInsSxMfNum());
                    }
                    //附加被保险付费人数
                    if (checkNumVO.getInsNum2().compareTo(0) > 0) {
                        checkNumVO.setInsFfNum(checkNumVO.getInsNum2() - checkNumVO.getInsMfNum());
                    }
                    //主险付费人数
                    checkNumVO.setInsNum6(checkNumVO.getGradeNum() - checkNumVO.getInsNum5());
                }
            }
        }
    }

    /**
     * 前端入参 实责人数计算
     *
     * @param paramList
     * @return
     */
    private List<CheckNumVO> szCheckZxNum(List<CheckNumVO> paramList) {
        // 前端没有给计算，所以后端需要处理下
        log.info("[全国实责]批改人数列表-----------------开始---------------");
        for (CheckNumVO checkNumVO : paramList) {
            log.info("年级：grade={}", checkNumVO.getGrade());
            Integer gradeNum = checkNumVO.getGradeNum();
            log.info("年级总人数：gradeNum={}", gradeNum);
            // 实责-附加实习和附加被保险同时投保人数
            Integer insNum3 = checkNumVO.getInsNum3();
            log.info("[附加实习]和[附加被保险]同时投保人数：insNum3={}", insNum3);
            // 实责-附加实习无过失附加险人数
            Integer insNum1 = checkNumVO.getInsNum1();
            // 只投保附加实习无过失附加险人数
            insNum1 = insNum1 - insNum3;
            checkNumVO.setInsNum1(insNum1);
            log.info("[附加实习无过失]人数：insNum1={}", insNum1);
            // 实责-附加被保险人人数
            Integer insNum2 = checkNumVO.getInsNum2();
            // 只投保附加被保险人人数
            insNum2 = insNum2 - insNum3;
            checkNumVO.setInsNum2(insNum2);
            log.info("[附加被保险人]人数：insNum2={}", insNum2);
//            实责-只买主险人数
            checkNumVO.setInsNum4(gradeNum - insNum1 - insNum2 - insNum3);
            log.info("[主险]人数：insNum2={}", gradeNum - insNum1 - insNum2 - insNum3);
        }
        log.info("实责批改人数列表-----------------结束---------------");
        return paramList;
    }

    /**
     * 前端入参 计算校责人数
     */
    private List<CheckNumVO> checkZxNum(List<CheckNumVO> paramList) {
        log.info("[全国校责]批改人数列表-----------------开始---------------");
        for (CheckNumVO checkNumVO : paramList) {
            /**
             * A-主险 B-校方 C-校外 D-传染病
             */
            log.info("年级：grade={}", checkNumVO.getGrade());
            Integer gradeNum = checkNumVO.getGradeNum();
            log.info("年级总人数：gradeNum={}", gradeNum);
            //ABCD
            Integer abcd = checkNumVO.getXz1Num();
            log.info("[主险+校方+校外+传染病]：abcd={}", abcd);
            //ABC
            Integer abc = checkNumVO.getXz2Num();
            //ABD
            Integer abd = checkNumVO.getXz3Num();
            //AB
            Integer ab = checkNumVO.getXzNum1();
            //AD
            Integer ad = checkNumVO.getXzcrbNum();
            //赋值计算
            //ABC
            checkNumVO.setXz2Num((abc - abcd) < 1 ? 0 : (abc - abcd));
            log.info("[主险+校方+校外]：abc={}", checkNumVO.getXz2Num());
            //ABD
            checkNumVO.setXz3Num((abd - abcd) < 1 ? 0 : (abd - abcd));
            log.info("[主险+校方+传染病]：abd={}", checkNumVO.getXz3Num());
            //AB
            checkNumVO.setXzNum1((ab - abc - abd - abcd) < 1 ? 0 : (ab - abc - abd - abcd));
            log.info("[主险+校方]：ab={}", checkNumVO.getXzNum1());
            //AD
            checkNumVO.setXzcrbNum((ad - abd - abcd) < 1 ? 0 : (ad - abd - abcd));
            log.info("[主险+传染病]：ad={}", checkNumVO.getXzcrbNum());
            //A
            checkNumVO.setXzNum2((gradeNum - checkNumVO.getXz1Num() - checkNumVO.getXz2Num() - checkNumVO.getXz3Num() - checkNumVO.getXzNum1() - checkNumVO.getXzcrbNum()) < 1 ? 0 : (gradeNum - checkNumVO.getXz1Num() - checkNumVO.getXz2Num() - checkNumVO.getXz3Num() - checkNumVO.getXzNum1() - checkNumVO.getXzcrbNum()));
            log.info("[主险]：a={}", checkNumVO.getXzNum2());
        }
        log.info("校责批改人数列表-----------------结束---------------");
        return paramList;
    }

    /**
     * 根据批改类型和险种组合的优先级获取险种组合
     * 规则优先级：实责[sub：D-C-B-A 批增add：A-B-C-D]
     * 校责[sub：J-I-H-G-F-E 批增add：E-F-G-H-I-J-K]
     *
     * @param type             A-双附加  B-附加实习无过失 C-附加被保险人  D-主险
     *                         E-校方+校外+传染病 F-校方+校外 G-校方+传染病 H-校方 I-传染病 J-主险 (校外不能单独投保)
     * @param endorsementsType
     * @return
     */
    private String getRiskTypeByEndType(String type, String endorsementsType, String productCode) {
        switch (productCode) {
            case SZ_PRODUCT_CODE://实责
                if (StringUtils.equals(endorsementsType, "add")) {
                    if (StringUtils.equals(type, "X")) {
                        type = "A";
                    } else if (StringUtils.equals(type, "A")) {
                        type = "B";
                    } else if (StringUtils.equals(type, "B")) {
                        type = "C";
                    } else if (StringUtils.equals(type, "C")) {
                        type = "D";
                    }
                } else {
                    if (StringUtils.equals(type, "Y")) {
                        type = "D";
                    } else if (StringUtils.equals(type, "D")) {
                        type = "C";
                    } else if (StringUtils.equals(type, "C")) {
                        type = "B";
                    } else if (StringUtils.equals(type, "B")) {
                        type = "A";
                    }
                }
                break;
            case XZ_PRODUCT_CODE://校责
                if (StringUtils.equals(endorsementsType, "add")) {
                    if (StringUtils.equals(type, "X")) {
                        type = "E";
                    } else if (StringUtils.equals(type, "E")) {
                        type = "F";
                    } else if (StringUtils.equals(type, "F")) {
                        type = "G";
                    } else if (StringUtils.equals(type, "G")) {
                        type = "H";
                    } else if (StringUtils.equals(type, "H")) {
                        type = "I";
                    } else if (StringUtils.equals(type, "I")) {
                        type = "J";
                    }
                } else {
                    if (StringUtils.equals(type, "Y")) {
                        type = "J";
                    } else if (StringUtils.equals(type, "J")) {
                        type = "I";
                    } else if (StringUtils.equals(type, "I")) {
                        type = "H";
                    } else if (StringUtils.equals(type, "H")) {
                        type = "G";
                    } else if (StringUtils.equals(type, "G")) {
                        type = "F";
                    } else if (StringUtils.equals(type, "F")) {
                        type = "E";
                    }
                }
                break;
        }
        return type;
    }

    /**
     * 每个险种对应的优惠人数
     *
     * @param resultList
     * @param grade
     * @param discountNum
     * @param type
     */
    private void GetNumOfDiscountByRiskType(List<CheckNumVO> resultList, byte grade, Integer discountNum, String type, String productCode) {
        switch (productCode) {
            case SZ_PRODUCT_CODE://实责
                switch (type) {
                    case "A"://双附加
                        for (CheckNumVO checkNumVO : resultList) {
                            if (StringUtils.equals(GradeEnum.getEnCodeByCode(grade), checkNumVO.getGrade())) {
                                if (checkNumVO.getSxAInsMfNum() != null && checkNumVO.getSxAInsMfNum().compareTo(0) > 0) {
                                    checkNumVO.setSxAInsMfNum(checkNumVO.getSxAInsMfNum() + discountNum);
                                } else {
                                    checkNumVO.setSxAInsMfNum(discountNum);
                                }
                            }
                        }
                        break;
                    case "B"://附加实习无过失
                        for (CheckNumVO checkNumVO : resultList) {
                            if (StringUtils.equals(GradeEnum.getEnCodeByCode(grade), checkNumVO.getGrade())) {
                                if (checkNumVO.getInsSxMfNum() != null && checkNumVO.getInsSxMfNum().compareTo(0) > 0) {
                                    checkNumVO.setInsSxMfNum(checkNumVO.getInsSxMfNum() + discountNum);
                                } else {
                                    checkNumVO.setInsSxMfNum(discountNum);
                                }

                            }
                        }
                        break;
                    case "C"://附加被保险人
                        for (CheckNumVO checkNumVO : resultList) {
                            if (StringUtils.equals(GradeEnum.getEnCodeByCode(grade), checkNumVO.getGrade())) {
                                if (checkNumVO.getInsMfNum() != null && checkNumVO.getInsMfNum().compareTo(0) > 0) {
                                    checkNumVO.setInsMfNum(checkNumVO.getInsMfNum() + discountNum);
                                } else {
                                    checkNumVO.setInsMfNum(discountNum);
                                }
                            }
                        }
                        break;
                    case "D"://主险
                        for (CheckNumVO checkNumVO : resultList) {
                            if (StringUtils.equals(GradeEnum.getEnCodeByCode(grade), checkNumVO.getGrade())) {
                                if (checkNumVO.getInsNum5() != null && checkNumVO.getInsNum5().compareTo(0) > 0) {
                                    checkNumVO.setInsNum5(checkNumVO.getInsNum5() + discountNum);
                                } else {
                                    checkNumVO.setInsNum5(discountNum);
                                }
                            }
                        }
                        break;
                }
                break;
            case XZ_PRODUCT_CODE://校责
                switch (type) {
                    case "E"://校方+校外+传染病
                        for (CheckNumVO checkNumVO : resultList) {
                            if (StringUtils.equals(GradeEnum.getEnCodeByCode(grade), checkNumVO.getGrade())) {
                                if (checkNumVO.getXz1MFNum() != null && checkNumVO.getXz1MFNum().compareTo(0) > 0) {
                                    checkNumVO.setXz1MFNum(checkNumVO.getXz1MFNum() + discountNum);
                                } else {
                                    checkNumVO.setXz1MFNum(discountNum);
                                }
                            }
                        }
                        break;
                    case "F"://校方+校外
                        for (CheckNumVO checkNumVO : resultList) {
                            if (StringUtils.equals(GradeEnum.getEnCodeByCode(grade), checkNumVO.getGrade())) {
                                if (checkNumVO.getXz2MFNum() != null && checkNumVO.getXz2MFNum().compareTo(0) > 0) {
                                    checkNumVO.setXz2MFNum(checkNumVO.getXz2MFNum() + discountNum);
                                } else {
                                    checkNumVO.setXz2MFNum(discountNum);
                                }

                            }
                        }
                        break;
                    case "G"://校方+传染病
                        for (CheckNumVO checkNumVO : resultList) {
                            if (StringUtils.equals(GradeEnum.getEnCodeByCode(grade), checkNumVO.getGrade())) {
                                if (checkNumVO.getXz3MFNum() != null && checkNumVO.getXz3MFNum().compareTo(0) > 0) {
                                    checkNumVO.setXz3MFNum(checkNumVO.getXz3MFNum() + discountNum);
                                } else {
                                    checkNumVO.setXz3MFNum(discountNum);
                                }
                            }
                        }
                        break;
                    case "H"://校方
                        for (CheckNumVO checkNumVO : resultList) {
                            if (StringUtils.equals(GradeEnum.getEnCodeByCode(grade), checkNumVO.getGrade())) {
                                if (checkNumVO.getXzMfNum() != null && checkNumVO.getXzMfNum().compareTo(0) > 0) {
                                    checkNumVO.setXzMfNum(checkNumVO.getXzMfNum() + discountNum);
                                } else {
                                    checkNumVO.setXzMfNum(discountNum);
                                }
                            }
                        }
                        break;
                    case "I"://传染病
                        for (CheckNumVO checkNumVO : resultList) {
                            if (StringUtils.equals(GradeEnum.getEnCodeByCode(grade), checkNumVO.getGrade())) {
                                if (checkNumVO.getXzcrbMFNum() != null && checkNumVO.getXzcrbMFNum().compareTo(0) > 0) {
                                    checkNumVO.setXzcrbMFNum(checkNumVO.getXzcrbMFNum() + discountNum);
                                } else {
                                    checkNumVO.setXzcrbMFNum(discountNum);
                                }
                            }
                        }
                        break;
                    case "J"://主险
                        for (CheckNumVO checkNumVO : resultList) {
                            if (StringUtils.equals(GradeEnum.getEnCodeByCode(grade), checkNumVO.getGrade())) {
                                if (checkNumVO.getXzNum3() != null && checkNumVO.getXzNum3().compareTo(0) > 0) {
                                    checkNumVO.setXzNum3(checkNumVO.getXzNum3() + discountNum);
                                } else {
                                    checkNumVO.setXzNum3(discountNum);
                                }
                            }
                        }
                        break;
                }
                break;
        }

    }

    /**
     * 获取指定险种组合下每个年级的人数
     *
     * @param resultList
     * @param type       A-双附加 B-附加实习无过失   C- 附加被保险人 D-主险
     *                   E-校方+校外+传染病 F-校方+校外 G-校方+传染病 H-校方 I-传染病 J-主险 (校外不能单独投保)
     * @return
     */
    private Map<String, Integer> getNumMapByRiskType(List<CheckNumVO> resultList, String type, String productCode) {

        Map<String, Integer> map = new HashMap<>();
        switch (productCode) {
            case SZ_PRODUCT_CODE://实责
                switch (type) {
                    case "A":
                        for (CheckNumVO checkNumVO : resultList) {
                            String nj = checkNumVO.getGrade();
                            Integer insNum1 = checkNumVO.getInsNum3();
                            map.put(nj, insNum1);
                        }
                        break;
                    case "B":
                        for (CheckNumVO checkNumVO : resultList) {
                            String nj = checkNumVO.getGrade();
                            Integer insNum1 = checkNumVO.getInsNum1();
                            map.put(nj, insNum1);
                        }
                        break;
                    case "C":
                        for (CheckNumVO checkNumVO : resultList) {
                            String nj = checkNumVO.getGrade();
                            Integer insNum2 = checkNumVO.getInsNum2();
                            map.put(nj, insNum2);
                        }
                        break;
                    case "D":
                        for (CheckNumVO checkNumVO : resultList) {
                            String nj = checkNumVO.getGrade();
                            Integer insNum4 = checkNumVO.getInsNum4();
                            map.put(nj, insNum4);
                        }
                        break;
                }
                break;
            case XZ_PRODUCT_CODE://校责
                switch (type) {
                    case "E"://校方+校外+传染病
                        for (CheckNumVO checkNumVO : resultList) {
                            String nj = checkNumVO.getGrade();
                            map.put(nj, checkNumVO.getXz1Num());
                        }
                        break;
                    case "F"://校方+校外
                        for (CheckNumVO checkNumVO : resultList) {
                            String nj = checkNumVO.getGrade();
                            map.put(nj, checkNumVO.getXz2Num());
                        }
                        break;
                    case "G"://校方+传染病
                        for (CheckNumVO checkNumVO : resultList) {
                            String nj = checkNumVO.getGrade();
                            map.put(nj, checkNumVO.getXz3Num());
                        }
                        break;
                    case "H"://校方
                        for (CheckNumVO checkNumVO : resultList) {
                            String nj = checkNumVO.getGrade();
                            map.put(nj, checkNumVO.getXzNum1());
                        }
                        break;
                    case "I"://传染病
                        for (CheckNumVO checkNumVO : resultList) {
                            String nj = checkNumVO.getGrade();
                            map.put(nj, checkNumVO.getXzcrbNum());
                        }
                        break;
                    case "J"://主险
                        for (CheckNumVO checkNumVO : resultList) {
                            String nj = checkNumVO.getGrade();
                            map.put(nj, checkNumVO.getXzNum2());
                        }
                        break;
                }
                break;
        }
        return map;
    }

    @Override
    public PageInfo getEndorseList(EndorseListParam param) {
        Weekend<EndorseEntity> weekend = new Weekend<>(EndorseEntity.class);
        WeekendCriteria<EndorseEntity, Object> criteria = weekend.weekendCriteria();

        criteria.andEqualTo(INSURANCE_ID, param.getInsuranceId());
        if (StringUtils.isNotBlank(param.getName())) {
            criteria.andEqualTo(NAME, param.getName().trim());
        }
        if (StringUtils.isNotBlank(param.getSex())) {
            criteria.andEqualTo(SEX, param.getSex());
        }
        if (StringUtils.isNotBlank(param.getCardNo())) {
            criteria.andEqualTo(CARD_NO, param.getCardNo().trim());
        }
        if (StringUtils.isNotBlank(param.getStudentTeacher())) {
            criteria.andEqualTo(STUDENTTEACHER, param.getStudentTeacher());
        }
        if (StringUtils.isNotBlank(param.getClz())) {
            criteria.andEqualTo(CLZ, param.getClz());
        }
        if (StringUtils.isNotBlank(param.getGrade())) {
            criteria.andEqualTo(GRADE, param.getGrade());
        }
        if (StringUtils.isNotBlank(param.getNameOrCardNo())) {
            criteria.andCondition("(" + NAME + " like '%" + param.getNameOrCardNo() + "%' or " + CARD_NO + " = '" + param.getNameOrCardNo() + "')");
        }
        criteria.andEqualTo(IS_DELETED, false);
        // 过滤状态为 已删除 和 已被替换 的数据
        criteria.andNotEqualTo(STATUS, StatusEnum.DELETE.getCode());
        criteria.andNotEqualTo(STATUS, StatusEnum.ORIGIN.getCode());

        Page page = PageParam.orderHandle(param);//PageHelper.startPage(param.getPn(), param.getPs()).setOrderBy(param.getOrderby());
        List<EndorseEntity> entities = endorseMapper.selectByExample(weekend);
        List<EndorseVo> list = EndorseVo.convertEntity(entities);
        PageInfo info = new PageInfo(page);
        info.setList(list);
        return info;
    }

    @Override
    public ResponseEntity analysisEndorse(String insuranceId) {

        String findCode = "analysisEndorse_" + System.currentTimeMillis();

        log.info("{} 案件关联保单时检查人员清单是否解析 投保单号：{}", findCode, insuranceId);

        try {
            if (insuranceId.startsWith("T")) {
                log.info("{} 案件关联保单时检查人员清单是否解析 新的投保单号码", findCode);
                // 查询是否为教保网保单，如果是开始解析
                GenericDataResponse<AppFormVO> appForm = insClient.queryAppMsg(insuranceId);
                if ("0000".equals(appForm.getCode())) {
                    log.info("{} 案件关联保单时检查人员清单是否解析 查询投保中心投保单字段接口成功", findCode);
                    if (appForm.getData() != null && "00".equals(appForm.getData().getChannelType())) {
                        log.info("{} 案件关联保单时检查人员清单是否解析 该投保单号为教保网投保单 投保单号：{}, channelType:{}",
                                findCode, insuranceId, appForm.getData().getChannelType());
                        // 1 根据投保单号查询save表是否存在记录
                        Weekend<SaveEndorseParam> weekend = new Weekend<>(SaveEndorseParam.class);
                        WeekendCriteria<SaveEndorseParam, Object> criteria = weekend.weekendCriteria();

                        criteria.andEqualTo(SaveEndorseParam::getInsuranceId, insuranceId);
                        criteria.andEqualTo(SaveEndorseParam::getIsIns, 1);

                        List<SaveEndorseParam> saveList = endorseParamMapper.selectByExample(weekend);

                        // 1.1 save表存在记录
                        if (saveList != null && saveList.size() > 0) {

                            log.info("{} 案件关联保单时检查人员清单是否解析 该投保单在save表存在记录", findCode);

                            List<SaveEndorseParam> result = new ArrayList<>();
                            saveList.forEach(s -> {

                                log.info("{} 案件关联保单时检查人员清单是否解析 该投保单在save表存在记录 遍历判断是否已经解析过 记录ID:{}, 是否解析过:{}",
                                        findCode, s.getId(), s.getIsDeleted());

                                Weekend<EndorseEntity> endorseWeekend = new Weekend<>(EndorseEntity.class);
                                WeekendCriteria<EndorseEntity, Object> endorseCriteria = endorseWeekend.weekendCriteria();

                                endorseCriteria.andEqualTo(EndorseEntity::getInsuranceId, insuranceId);

                                List<EndorseEntity> endorseList = endorseMapper.selectByExample(endorseWeekend);
                                if (endorseList != null && endorseList.size() > 0) {

                                    log.info("{} 案件关联保单时检查人员清单是否解析 该投保单在endorse表存在记录 证明已经解析成功", findCode);

                                    // endorse表存在该投保单清单人员信息，证明已经解析成功
                                } else {// endorse表不存在该投保单清单人员信息，证明未经解析成功，重新解析

                                    log.info("{} 案件关联保单时检查人员清单是否解析 该投保单在endorse表存在记录 证明未解析成功 需要重新解析", findCode);

                                    result.add(s);
                                }
                            });
                            if (result != null && result.size() > 0) {
                                insuranceEffect(findCode, result);
                            }
                        } else { // 1.2 save表不存在记录

                            log.info("{} 案件关联保单时检查人员清单是否解析 该投保单在save表不存在记录 需要插入新记录", findCode);

                            AppDetailProParam p = new AppDetailProParam();
                            p.setAppCode(insuranceId);
                            p.setChannelCode("edu");    // 渠道编号
                            ResponseEntity<GenericDataResponse<AppDetailProVo>> appDetailPro = insClient.searchAppDetailPro(p);

                            AppDetailProVo detailVos = appDetailPro.getBody().getData();
                            HolderForProVO holder = detailVos.getHolder();
                            String holderCode = holder.getHolderCode();

                            SaveEndorseParam param = new SaveEndorseParam();
                            param.setInsuranceId(insuranceId);
                            param.setIsDeleted(false);
                            param.setIsIns(1);
                            param.setCreateTime(new Date());
                            param.setUsercode(holderCode);

                            log.info("{} 案件关联保单时检查人员清单是否解析 该投保单在save表不存在记录 查询该投保单投保人userCode:{}", findCode, holderCode);

                            int num = endorseParamMapper.insertSelective(param);
                            if (num > 0) {

                                log.info("{} 案件关联保单时检查人员清单是否解析 该投保单在save表不存在记录 插入新记录成功", findCode);

                                List<SaveEndorseParam> list = new ArrayList<>();
                                list.add(param);
                                insuranceEffect(findCode, list);
                            }
                        }
                    } else {
                        log.info("{} 案件关联保单时检查人员清单是否解析 该投保单号为我学保投保单 投保单号：{}, channelType:{}",
                                findCode, insuranceId, appForm.getData() != null ? appForm.getData().getChannelType() : "appForm.getData()为空");
                    }
                } else {
                    log.info("{} 案件关联保单时检查人员清单是否解析 查询投保中心投保单字段接口失败 code:{},msg:{}",
                            findCode, appForm.getCode(), appForm.getMsg());
                }
            } else {//老投保单，清单文件格式与现有格式不一致
                log.info("{} 案件关联保单时检查人员清单是否解析 老的投保单号码", findCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("{} 案件关联保单时检查人员清单是否解析 接口异常 {}", findCode, e);
        }

        return GenericDataResponse.ok();
    }

    private <T> List<T> deepCopyList(List<T> list) {
        List<T> copyList = null;
        try {
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(byteOut);
            out.writeObject(list);
            ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
            ObjectInputStream in = new ObjectInputStream(byteIn);
            copyList = (List<T>) in.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return copyList;
    }

    /**
     * 计算校责险批改 批单中心所需的人数
     */
    private List<EndorseEntity> getEndorsesByIns(String insuranceId, String userCode) {
        List<EndorseEntity> endorses = null;
        if (redisTemplate.hasKey(PAUSE)) {
            log.info("定时任务正在执行，主流程直接跳过处理");
            return null;
        }
        log.info("查询不到数据，有可能是投保中心已经出单但是信息没有同步到edu-svc服务");
        //设置一个定时任务暂停器，防止两边同时向数据库添加数据
        //5秒的一个key
        redisTemplate.opsForValue().set(PAUSE + insuranceId, PAUSE, 5L, TimeUnit.SECONDS);

        ResponseEntity<GenericDataResponse<AppDetailProVo>> appDetailPro = null;
        AppDetailProVo data = null;
        AppDetailProParam p = new AppDetailProParam();
        p.setAppCode(insuranceId);
        p.setChannelCode("edu");    // 渠道编号
        //p.setUserCode(userCode);
        try {
            appDetailPro = insClient.searchAppDetailPro(p);
            if (appDetailPro == null) {
                log.error("endorse表和投保中心都没有查出数据，insuranceId={}", insuranceId);
                return null;
            }
            data = appDetailPro.getBody().getData();
            if (data == null) {
                log.error("endorse表和投保中心都没有查出数据，insuranceId={}", insuranceId);
                return null;
            }
        } catch (Exception e) {
            log.error("endorse表没有查出数据，投保中心查询也失败，insuranceId={}", insuranceId);
            e.printStackTrace();
            return null;
        }
        //出单状态  待出单（08）出单失败(09) 已出单（10）
        String issueType = data.getApplicationStatus();
        if (StringUtils.equals(issueType, "10")) {
            PolicyTypeEnum policyTypeEnum = PolicyTypeEnum.getByProductCode(data.getProductCode());
            boolean policyTypeFlag;
            if (policyTypeEnum == null) {
                log.error("不能识别的险种信息{}", data.getProductCode());
                log.error("endorse表没有查出数据，投保中心查询也失败，insuranceId={}", insuranceId);
                return null;
            } else policyTypeFlag = policyTypeEnum == PolicyTypeEnum.TRAINEE_ENT
                    || policyTypeEnum == PolicyTypeEnum.TRAINEE_SCH
                    || policyTypeEnum == PolicyTypeEnum.STUDENT_SCH;


            int i = 0;
            if (policyTypeEnum != PolicyTypeEnum.YN_SP) {
                SaveEndorseParam endorseParam = new SaveEndorseParam();
                endorseParam.setInsuranceId(insuranceId);
                endorseParam.setIsIns(1);
                List<SaveEndorseParam> saveEndorseParamLists = endorseParamMapper.select(endorseParam);
                //查看是否有记录 如果没有就插入一条
                if (CollectionUtils.isEmpty(saveEndorseParamLists)) {
                    endorseParam.setUsercode(data.getHolder().getHolderCode());
                    endorseParam.setIsDeleted(false);
                    endorseParam.setCreateTime(new Date());
                    i = endorseParamMapper.insertSelective(endorseParam);
                    if (i < 1) {
                        log.error("saveEndorseParam表保存数据失败，insuranceId={}", insuranceId);
                        return null;
                    }
                }
                i = saveInsurance(Collections.singletonList(insuranceId), data.getHolder().getHolderCode(), policyTypeFlag, false);
            }
            if (i > 0 || policyTypeEnum == PolicyTypeEnum.YN_SP) {
                SaveEndorseParam endorseParam = new SaveEndorseParam();
                endorseParam.setInsuranceId(insuranceId);
                endorseParam.setUsercode(data.getHolder().getHolderCode());
                endorseParam.setIsIns(1);
                endorseParam.setIsDeleted(true);
                endorseParam.setCreateTime(new Date());
                i = endorseParamMapper.insertSelective(endorseParam);
                if (i < 1) {
                    log.error("saveEndorseParam表保存数据失败，insuranceId={}", insuranceId);
                    return null;
                }
            }
        }
        //再次查询
        return getEndorses(insuranceId);
    }

    @Override
    public List<EduPolicyVO> queryProEndorseList(EndorsementsEduQueryVO endorsementsEduQueryVO) {
        try {
            //去掉分页设置
            endorsementsEduQueryVO.setPageSize(10000);
            GenericListResponse<EduPolicyVO> rets = polClient.queryEduEndorsementsInfoList(endorsementsEduQueryVO);
            if (null != rets && !CollectionUtils.isEmpty(rets.getList())) {
                return rets.getList();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Lists.newArrayList();
    }


    @Override
    public List<EndorsQueryResp> endorsementConformList(EndorsQueryParam endorsQueryParam) {
        try {
            //去掉分页设置
            endorsQueryParam.setPs(10000);
            endorsQueryParam.setPn(1);
            ResponseEntity<GenericListResponse<EndorsQueryResp>> rets = polClient.endorsementConformList(endorsQueryParam);
            if (null != rets && !CollectionUtils.isEmpty(rets.getBody().getList())) {
                return rets.getBody().getList();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Lists.newArrayList();
    }

    @Override
    public void downEndorPdf(WebContext ctx, String flag, Model model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String html = "";
        if ("1".equals(flag) || "2".equals(flag)) {
            html = "addAndLess";
        } else if ("4".equals(flag)) {
            html = "general";
        } else if ("3".equals(flag)) {
            html = "replace";
        } else if ("5".equals(flag)) {
            html = "refund";
        }
        String htmlcontext = templateEngine.process(html, ctx);
        log.info("下载批单PDF,html ：{}", html);

        String path = "批单" + ".pdf";
        response.addHeader("Content-Disposition",
                "attachment;fileName=" + URLEncoder.encode(path, "UTF-8"));
        response.setHeader("Content-Type", "application/pdf");
        ServletOutputStream outputStream = response.getOutputStream();

        outputStream.write(Html2PdfUtil.convert(htmlcontext));
        outputStream.close();
        log.info("下载投保单生成PDF完成");
    }

    @Override
    public int deleteByApplyId(String applyId) {
        SaveEndorseParam saveEndorseParam = new SaveEndorseParam();
        saveEndorseParam.setApplyId(applyId);
        saveEndorseParam.setIsDeleted(false);
        try {
            //正常只会有一条记录，如果有多条，表示错误数据
            SaveEndorseParam saveEndorseParam1 = endorseParamMapper.selectOne(saveEndorseParam);
            if (null == saveEndorseParam1) {
                return 0;
            }
            saveEndorseParam.setId(saveEndorseParam1.getId());
            saveEndorseParam.setIsDeleted(true);
            return endorseParamMapper.updateByPrimaryKeySelective(saveEndorseParam);
        } catch (Exception e) {
            log.error("批单申请号：{}，查询失败。", applyId, e);
            return -1;
        }
    }

}
